このアプリケーションは、画面を操作するとデータの集録を開始したり終了したりする。あるいは、データ集録開始後、一定時間が経過すると、自動的に集録を終了する。こういうのは、Producer/Consumer パターンで実装するのが、LabVIEW では定石みたいになっていると思う。
( image from http://www.ni.com/white-paper/3023/en/ )
ところがサンプリングレートやタイミングが異なるような、複数のデバイスからデータを集録するようなときは、ちょっとだるい。
たとえば
- アナログ電圧のデバイスから、サンプリングレート 10k/s で 0.1秒ごとのかたまりで取得
- シリアルポートに繋がった電流計から、0.7秒ごとに電圧を取得
- CAN ポートにメッセージが到達するごとに集録(いつ届くかは分からない。届かないこともある)
- デジタル信号のデバイスから(以下略)
というのを並行処理すると考える。ループのタイミングが違うので、アナログ、シリアル、CAN で、それぞれ別のループを作ることになるであろう。すると、各ループは、事実上、別スレッドで動くアクターになる。
やりすぎ感
NI のサンプルとか、フォーラムを見てると、ここも Producer/Consumer として実装している感じがする。だけど、ちょっと、だるい。
Consumer ループというのは、実装としてはイベントストラクチャを内包するループなんだけど、ロジック上はステートマシンになっている。でも集録アクターにおいて、複雑な状態遷移は起こらない。
デバイスの初期化 → 集録開始 → データ取得 → データ取得 ... → 集録停止
データ集録を繰り返す以外は、一直線に遷移するだけなのに、ステートマシンとは大げさな。初期化して開始して、ずーっと集録しつづけて、外部からトリガがかかったときに停止すればよい。
途中でエラーが出たときに、呼び出し側が丁寧に対処することは、ほとんどない。複数の信号ソースを同期しながら集録するようなアプリケーションで、どこかひとつのチャンネルでエラーが出たら、その回のデータは使えないのだ。
アクターフレームワーク
あと、アクターフレームワークというパレットに、いくつかVIがある。もともとサードパーティのものをカジュアルに同梱しただけらしく、ドキュメントがない。クラスを使っているんだけど、どのメソッドが必須なのかとかも書かれていない。ソースを読んだところ、ちょっと大げさな感じであった。
というわけで、もうちょいシンプルに書いた/描いた。
Actor VI
各デバイスのデータ集録のプログラムを actor.vi と呼ぼう。
上半分は同期とか、並行処理とか考えず、単純に電圧を集録するプログラムになっている。サンプルプログラムほとんどそのままだ。
下半分にキューがあって、終了するかどうかを決定する。終了する条件は以下のいずれか。
- なんかエラーが出た
- 参照しているキューが破棄された
- タイムアウトなしで、デキューできた (=停止メッセージを受け取った)
ループを脱け出したら、デバイスの片付けをして終了する。
Main VI
呼び出し側は、以下のような感じ。
まずキューを取得し、ここではサンプリングレートとともに、Actor VI に渡す。もっとたくさん設定項目があれば、それも渡せば良い。そして Actor VI を実行する。
ここでは0.1秒の遅延をさせているけれど、実際にはユーザーインターフェースの停止ボタン待ちだったりする。
Actor VI を止めたくなったら、エンキューする。すると、Actor ではデキューに成功して、ループを抜け出して、終了することになる。
キューの管理
このサンプルでは、呼び出し側がエンキューの直後に、キューを開放しているんだけど、実際のアプリケーションでは、そんなにすぐに開放しない。ユーザー操作待ちの時間があったり、もういちど Actor VI を動かす処理が入ったりする。
キューの取得と開放は、Actor 側でもできるんだけど、あえてやっていない。一旦取得したキューを開放せずに VI が終了することを繰り返すと、毎回数バイトがガーベッジコレクターに回収されずにたまっていく。開発の途中で、メモリが増えていくことに気付いて困ったので、「気をつけて開放するようにコードを書く」のではなくて、「開放のことをほとんど考えなくていいコードにする」ということにした。
Go~
呼び出し側から、停止命令を伝えるための経路を渡し、呼び出され側は停止命令が来るまで動くっていうのは、Go のコードで何度か見かけた。チャンネルを渡すやつですね。Go に限らず、アクターにメッセージ渡す系の書き方だとこうなるのであろう。
先月リリースされた LabVIEW 2016 には、並行処理ループ間でのメッセージ渡しを、簡単に記述できる、まさに「チャンネルワイヤ」というのが入ったらしい。ほほう、遂に。