2016-09-20

LabVIEW のシンプルな並行処理制御

LabVIEW で、複数の並行処理を開始/停止させるアプリケーションを開発する機会があったので、メモ。

このアプリケーションは、画面を操作するとデータの集録を開始したり終了したりする。あるいは、データ集録開始後、一定時間が経過すると、自動的に集録を終了する。こういうのは、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 には、並行処理ループ間でのメッセージ渡しを、簡単に記述できる、まさに「チャンネルワイヤ」というのが入ったらしい。ほほう、遂に。