LabVIEWを触ったことがない方に向けて、それなりのプログラムが書けるようになるところまで基本的な事柄を解説していこうという試みです。
シリーズ29回目としてキューメッセージハンドラデザインパターンを扱います。
この記事は、以下のような方に向けて書いています。
- 複数あるデザインパターンの特徴を知りたい
- キューメッセージハンドラって何?
- キューメッセージハンドラの実例を知りたい
もし上記のことに興味があるよ、という方には参考にして頂けるかもしれません。
なお、 前回の記事はこちらです。
これまで出たデザインパターン
LabVIEW初心者の方へ、基本的な使い方を紹介してきたまずこれのシリーズで、これまでにいくつかのデザインパターンを紹介してきました。
最も単純なものは、特別な名前がありませんが、「左から右に」順番に実行して終わるタイプのデザインです。途中繰り返しの構造であるWhileループやForループなどを挟んだとしても、全体を見れば毎回ある特定の順番で動作を行う、という構造を持っています。
次に代表的なものはステートマシンです。一気にプログラムで組めることが増え、状態遷移図で書けるようなプログラムであればまずはこのデザインが適用できないか検討します。
LabVIEWを学ぶ上での最初の山場ですね。
より複雑なデザインパターンを作る上でもステートマシンが一部分に使用されていたりするので、LabVIEWでそれなりのプログラムを書く場合にはこのステートマシンの考え方の理解は避けて通れません。
この次の段階として紹介したものは生産者消費者デザインパターンです。キュー関数を使用して複数のWhileループを動かし、それぞれ専用の処理を行わせてプログラム全体の処理の効率を高めることができます。
キュー関数の特徴さえわかってしまえば、組み方に慣れる必要はありますが、そこまで複雑な仕組みでもないと思います。並列処理のプログラムを簡単に組める、というLabVIEWの特長を実感できる、強力なデザインパターンです。
そして今回の記事で紹介するのがキューメッセージハンドラデザインです。Queue Message Handlerの頭文字をとってQMHなどと呼ばれるので、以降はQMHと表記します。
QMHはキューという名前が使われている通り、キュー関数を使用します。そして、部分部分を見ると生産者消費者デザインが使用されています。そしてその中にさらにステートマシンの構造が含まれている場合もあります。
そのため、QMHは「ステートマシンも含めた、より複雑な生産者消費者デザイン」のようなものになっています。
一応補足をしておくと、QMHそのものにステートマシンは必須ではありません。結果的にステートマシンの考え方で組む部分が含まれることが多いのですが、QMHの本質的な部分は別のところにあります。
この記事の後半で具体的なQMHのデザインを用いたプログラムの例を紹介しますが、まずはQMHのデザインを理解する上で本質を理解するためにそれぞれの「要素」について考えてみます。
QMHの仕組み1:キューを改めて考える
まずはキューについてです。まずこれの第23回で初めて紹介しましたが、これはあるWhileループから別のWhileループにデータを渡す際に使用するものでした。
これを踏まえてキューの重要な性質としては、以下が挙げられます。
- エンキューされたデータは、同じキュー(リファレンス)に紐づいたデキューからしか取り出されない
- 順番が固定されている(欠損がない)
キューについて理解している人にとっては、何を当たり前のことを・・・と思うかもしれませんが、この性質が重要になっています。
キューには名前を付けて区別することができますが、基本的にはrefnumのワイヤを直接配線してキュー同士を区別することが多いです。
順番が固定され、FIFO (First in First out)の原則があることから、何か特定の順番を指定してデータを片方のループからもう片方のループに渡すことができます。また、必要に応じて優先させたいデータをキューの先頭にエンキューすることができるのも便利です。
ループ間でデータを渡す仕組みとしてノーティファイアというものもありますが、QMHについてはキューの上記の性質を必要とします。
QMHの仕組み2:メッセージとは
QMHのMはメッセージのMです。
ただ単にメッセージと聞くと、何かテキスト、文字列を表わすだけのように聞こえますが、プログラミングをする上ではもう少し特別な意味があり、「ある機能から別の機能に渡す、処理の内容やその処理に必要なデータなどの情報」を表わします。
ステートマシンを思い出してください。ステートマシンは(よほど複雑でない限り)一つのWhileループがあり、その中にケースストラクチャがあって状態遷移を表すデザインパターンでした。
少し説明が乱暴ですが、このWhileループが一つあれば(状態遷移図で表すことができる)何らかの「機能」を構成することができる、と言えなくもないですね。
このステートマシンが、上記のメッセージの説明の「機能」を表わすとしたら、「ある機能から別の機能に」情報を「渡す」というと・・・そう、ここにキューが使用されます。
実際は、この「機能」は必ずしもステートマシン同士を意味するわけではないのですが、とにかく何らかの一連の処理を担う「機能」と呼ばれるもの同士をキューで結び、それぞれで必要な処理の内容やその処理に必要なデータの受け渡しを行うのがメッセージとなります。
イメージとしては以下の図のような形となります。なんだかキューがたくさん出てくるのでワイヤがゴチャつきそうだな、と思われるかもしれませんが、実際のQMHだともう少しキューの扱い方を工夫することになります。
この受け渡しされるデータとは特定の型が決まっていない場合も多くあると思います。
数値の配列であることもあれば、フラグを渡すためにブールを扱ったり、あるいは特殊な構造を持ったクラスタを受け渡す場合も出てきます。
キューは一つのデータタイプしか扱わないので、異なる型のデータの受け渡しを行うたびにキューを用意する必要があるのか・・・というとそうではなく、ここでバリアントデータが活躍します。「何それ聞いたことない」という方は、以下の記事を参考にしてみてください。
ループ間でバリアントを使ってデータをやり取りする際の大まかなイメージは例えば以下の図のような使い方を想像してもらえばいいかなと思います。
このように、受け渡すデータの種類を特定せずに機能間でデータを送受してプログラム全体を構成する際にメッセージ(とこれに付随するメッセージデータ)を使用していきます。
QMHの仕組み3:ハンドラとは
最後にQMHのHやハンドラを表わします。
ハンドラ、似た言葉で車のハンドルというのがありますが、車の制御を行うためのものがハンドルであるように、ハンドラはプログラム全体の流れを制御する仕組みのことです。
QMHのプログラムに当てはめて考えると、プログラムの中に全体の流れを制御するためのハンドリング機能が実装されており、このハンドリング機能によりプログラムが様々な動作をすることになります。
ただし、常にハンドリング機能からの命令がないと他の機能が進まない(待機状態)になっているわけではありません。以下で紹介するサンプルでもそうですが、「きっかけ」こそハンドリング機能からもらっても、その後は自分自身で処理(状態遷移)を進めていったりします。
つまり今までの話を総合すると、プログラム全体の流れを制御するための「ハンドリング」という仕組みを持った部分があり、これが例えばステートマシンで作った「機能」とキューを介して「メッセージ」をやりとりしてプログラム全体が動く、それがQMHの実体です。
QMHの全体イメージ
QMHがどのようなものか何とな~くイメージがついたでしょうか?
「概念だけじゃ全然わからんよ」というわけで、少しでもより具体的にイメージできるように早速QMHの例を見ていきます。
本来は特別区別されるものではない(このブログ以外のLabVIEWの説明をしている資料で見たことがない)と思いますが、私はQMHは規模によってレベル(実装の難易度)があると思っています。そこで、各レベルに沿って少しずつQMHに慣れることにします。
QMHレベル1
例によってNIサンプルファインダの中にある「キューメッセージハンドラの基礎」というサンプルが、最も簡単な作りのQMHになっているのでこれを見ていきます。
ブロックダイアグラムを見ると、二つのWhileループがあり、それぞれイベント処理ループ、メッセージ処理ループと書かれています。
これらのループがそれぞれ「機能」を持っていると考えてください。つまり、「イベント処理ループ」は「イベントを処理するという機能」、「メッセージ処理ループ」は「メッセージを処理するという機能」を持っています。
これらの間がキューで結ばれています。そのキューではどういったデータをやり取りするかというと、文字列とバリアントがセットになったクラスタが指定されています。
メッセージの説明で書いた、「処理の内容やその処理に必要なデータ」ということに関し、
- 文字列・・・処理の内容(メッセージ)
- バリアント・・・処理に必要なデータ(メッセージデータ)
をそれぞれ保持する、という対応になっています。このクラスタをメッセージとして、二つの機能ループでやりとりされています。
このブロックダイアグラムを見て、「え、これって普通の生産者消費者デザインと何が違うの?」と思った方もいると思います。その感覚はとても大事だと思います。
なぜなら、上でも書いたように、QMHは「ステートマシンも含めた、より複雑な生産者消費者デザイン」だからです。列挙体やシフトレジスタを使った、オーソドックスなステートマシンではありませんが、メッセージ処理ループではイベント処理ループによって渡されたメッセージを基に各ステート(ケースストラクチャのケース)が実行されています。
そしてそれらのステートに必要に応じて渡されるデータがメッセージデータとなっています。
プログラムの流れを見るためにプログラムを実行すると、まずは初期化処理として「メッセージを表示」表示器の中身が空になります。
次に「動作1」のボタンを押したとします。すると、イベント処理ループでは、これに対応するイベント、「動作1の値変更」が実行され、キューに「Action1」というメッセージが入れられます。
メッセージ処理ループではこのメッセージを受け取り、Action1に相当するケースを実行します。このサンプルの場合ではUpdate Displayというメッセージを同じキューに入れ、その際のメッセージデータとして「「動作1」ボタンが押された」という文字列をバリアントにして渡しています。
すると、ステートマシンでいう「シフトレジスタに値が渡された」状態と同様、メッセージがエンキューされたことでメッセージ処理ループがイベント処理ループからの指示無しにもう一度実行され、今度はUpdate Displayのケースが実行されるようになります。
このケースでは受け取ったメッセージデータをバリアントから元のデータ型(文字列)に戻し、「メッセージを表示」に渡しています。それ以外特にキューに対する操作は行っていないので、メッセージ処理ループは再びイベント処理ループからの指示を待つ状態となります。
この流れは「動作2」のボタンを押した際も同様です。動作2専用のメッセージ(Action2)が処理されることになります。
そして停止ボタンが押された際の処理も、Exitというメッセージをメッセージ処理ループに渡してプログラムを停止させるような処理を行っていきます。
よりレベルの高いQMHを一番最初に見るとかなり面食らうと思いますが、このNIサンプルファインダの中のサンプルはQMHでありながらその仕組みを最も単純に理解するには最適だと思います。
QMHレベル2
さて、少し難易度を上げてもう少し複雑なQMHの例を見ていきます。こちらもサンプルなのですが、NIサンプルファインダにあるのではなく、プロジェクトテンプレートにあります。
プロジェクトエクスプローラ上にいくつか仮想フォルダなどありますがいったんおいておいて、とりあえずMain.viを見てみます。
ブロックダイアグラムを見ると、レベル1のプログラムと比べて見た目が少し複雑そうですが、そうはいってもWhileループは二つしかないので、「生産者消費者デザインパターン」が大元になっていると考えたらそれほど恐れる必要もありません。
ただし、QMHらしさを出すためのいくつかの機能が追加されています。
まずは見たことないキューの関数です。これらは関数パレットには表示されないのですが、プロジェクトを作成した際に付属してきます。
普通のキューと区別して「メッセージ専用キュー」とでも呼びますが、それぞれの中身を見るといつものキュー関数が使われているだけです。メッセージ専用キューという初めて知らない関数が出てきたわけですが、何も恐れることはない、「見かけ倒し」のキュー関数です。
実際には、さらに上のレベルのQMHだとこれらのメッセージ専用キューが大活躍するのですが、このテンプレートでは初めましてなので、やっていることも大したことはしていません。
例えば、キューを用いたプログラムで一番最初に使用するキュー取得の関数の代わりになる「すべてのメッセージキューを作成」では、このプログラムで使用する複数のキューを一度に作成、それらのリファレンスをクラスタとして束ねています。
実はこのテンプレートサンプルではキューを一種類(「UI」と名の付いたリファレンスを持つキュー)しか使用していないのですが、QMHではこのように複数のキューを用意してそれらのリファレンスをクラスタにするという方法を良く用います。
他にも、エンキューやデキューにあたる関数がメッセージ専用キューに用意されていますが、特別難しいことはなく、QMHで扱えるようにメッセージやメッセージデータを受け付けられるようにカスタマイズされているだけです。
なので、見た目は見慣れないキュー関数なのですが、その実体はとにかく、「QMH用にメッセージやメッセージデータを扱いやすくしたキュー関数」とだけ覚えておけばOKです。
そしてQMHらしさがもう一つ出ているのは、これまた関数パレットには表れてこない、ユーザイベントを操作する仕組みです。
ユーザイベントはまずこれシリーズの本編では扱っていなかったのですが、以下の記事で紹介しています。
やっていることは単純で、メッセージ処理ループからイベント処理ループを終わらせるための仕組みになっているだけです。
通常、イベントストラクチャはユーザからのイベント(フロントパネル上のブールボタンが押された、など)があると実行され、イベントがなければ実行されません。
プログラム的にイベントを駆動させるための仕組みを設けているためにこのような書き方がされています。
仕組みとしては、まずユーザイベントを生成しておきます。(見た目がノーティファイアに関する関数に似ているので注意!)
ユーザイベントデータタイプは様々なデータタイプを受け付けられますが、サンプルではイベント(ストラクチャ)を停止するためのフラグを扱うためにブールデータイプが使用されています。
このユーザイベントは、フロントパネル上で停止ボタンが押されたときに実行されるExitケース内の関数が実行されることで生成されます。
これによりユーザイベントがイベントストラクチャで実行され、イベントストラクチャの入ったループを停止させます。
見慣れない関数の説明は以上です。次にプログラムの流れを見ていきます。といってもこれ、実はNIサンプルファインダにあった「キューメッセージハンドラの基礎」とやっていることはほぼ同じです。
実行すると、二つのWhileループのうち、メッセージ処理ループの中のInitializeが必ず実行されます。え、いつの前にイベントが起きたんだと思うかもしれませんが、メッセージ専用キューの関数の中身をよく見ると実は既に一つメッセージがエンキューされています。
その後はユーザーが起こすイベント、このテンプレートで言えばフロントパネル上の各種ボタンを押すという操作、によってイベント処理ループのイベントストラクチャが反応し、メッセージ処理ループが駆動します。
なお、メッセージ処理ループの中にErrorステートがありますが、これは他の全てのステートに対して何かしらのエラーが起きた場合に実行されるステートになっています。
一つ一つの流れを落ち着いて考えると、なんだただの生産者消費者デザインパターンじゃん、と思えるようになったらもう十分だと思います。
QMH恐るるに足らず!
ちなみに、このキューメッセージハンドラのテンプレートは、「これからキューメッセージハンドラでプログラムを作ろう」という場合のベースとして使用することができます。
メッセージ専用キューだったりユーザイベントによるイベントストラクチャループの停止の仕組みなどが既に備わっているので、これをベースにプログラムを作るのが楽な場合にはこれを利用します。
さて、レベルは2までではなく、この後QMHレベル3が待ち構えています。
ここまでの説明だと、レベル1やレベル2は実際はQMHの恩恵をあまり実感できていないと思います。見た目こそ少し複雑ですが、しょせんは生産者消費者デザインパターンの延長になっているだけですよね。
そして実際レベル3であっても似たようなものなのですが、なかなか複雑になり、いよいよQMHの真価が見えてきます。
少し説明が長くなるので、次回の記事で紹介したいと思います。
もしよろしければ次の記事も見ていってもらえると嬉しいです。
ここまで読んでいただきありがとうございました。
コメント