LabVIEWを触ったことがない方に向けて、それなりのプログラムが書けるようになるところまで基本的な事柄を解説していこうという試みです。
シリーズ24回目としてノーティファイアを扱っていきます。
この記事は、以下のような方に向けて書いています。
- ノーティファイアって何?
- 3つ以上のループで値を共有する方法は?
- 生産者・消費者デザインパターンがわからない
もし上記のことに興味があるよ、という方には参考にして頂けるかもしれません。
なお、前回の記事はこちらです。
ノーティファイアとは
前回の記事で、キューが便利に使えることを紹介しました。でも一方で、3つ以上のループと値を共有することができないという話もしました。「チューブ(キュー)から値を取り出すのはいいとして、それだと二つのループ間でしか値を共有できなくない?」と。
そうなんです、チューブの中に要素を入れて、出した後は当然チューブの中にはもはやそのデータがないので、3つめ、4つめのループがあった場合にはそれらに対して同じデータを渡すことができません。
もちろん、前回の記事で紹介したようにキューを複数用意するという方法もあります。実際、キュー自体に名前を付けることで、別々のチューブを用意することが可能です。これらは独立しているので、チューブから取り出した値をまた今度は別のチューブに移して、・・・を繰り返せば3つ以上のループで共有するといった考えです。
でも、LabVIEWは別の関数で、3つ以上のループでも値を共有する方法を用意しています。それがノーティファイアです。
「3つ以上でも値を共有できるものがあるなら、キューは必要なくない?」という疑問を持った方、キューとノーティファイアはそれぞれメリット、デメリットがあります。なのでノーティファイアがあったとしてもキューもちゃんと存在意義があります。
ノーティファイアの仕組みについて
ノーティファイアについても、実際にプログラムに入る前にイメージをつけておきます。ノーティファイアは、テレビ局と各家庭の(録画機能のない)テレビのような関係を想像します。
テレビ局から、電波を通して映像というデータを各家庭のテレビに渡せば、どの家庭でも同じ映像を得る(見る)ことができますよね。
ただし、ここで想像しているテレビ自体には録画機能がありません。そのため、ある一か所のテレビで何らかの理由である瞬間の映像が取れていない場合、その間にテレビ局から新しい映像が流れてきたとしても、テレビは最新の映像しか映さず、取れていなかった映像(データ)は失われてしまいます。
ノーティファイアはまさにこのような性質を持ちます。もう少しLabVIEW寄りの表現をすると、
- 複数のループでデータを共有できるものの、最新の値しか共有されない
ということです。
実際の例を見る方が分かりやすいと思いますので、ノーティファイアの実体を見ていきます。実は、プログラムの組み方は関数の名前が異なるだけで、キューととても良く似ています。
ブロックダイアグラムを見てください。キューと構造が似ていませんか?どちらもループ間で値を共有できるという特徴を持つため、プログラムの構造も必然的に似てくるのは自然なことです。
なお、前回のキューの話では、要素を入れるループではケースストラクチャを使用し、今回のノーティファイアではデータを送信する側でイベントストラクチャを使用していますが、この辺りの組み方は自由です(キューにはケースストラクチャ、ノーティファイアにはイベントストラクチャを使用しなければいけないといった制限はありません)。
違いを明らかにするために、ノーティファイアのプログラムの方はループが三つになっていて、一番上が値を入れている状態、下二つが値を出しているような状態にしています。
上のプログラムで、実際に「送信データ」と「ループ1」「ループ2」の数値表示器の値はどれも一致します。では、最新の値しか共有されない、という注意書きはどこにいったのか?
それを知るためにまずはもう少し細かくプログラムの中身を見ていきます。まず、ノーティファイアを取得の関数があります。これはキュー生成と同じような位置づけですね。テレビ局を作ります。キューと同じようにデータの種類を決めます。
次に、ノーティフィケーションを送信の関数があります。これはキューで言う要素をエンキューに対応します。テレビ局から映像データを送信する、という状態です。
そして、ノーティフィケーションを待機の関数でデータを受けとります。キューでは要素をデキューがありました。各家庭のテレビが映像データを受け取っている状態です。要素を取り出す、という表現ではなく、データが来るのを文字通り待機する関数です。
最後にあるノーティフィケーション解放の関数は、キュー解放の関数に対応します。テレビ局をつぶす(?)というイメージになるでしょうか(少し乱暴ですが)。
テレビは何台あってもいいので、ループごとにノーティフィケーションを待機を用意できそれぞれでノーティフィケーション送信されたデータを受け取れます。
しかし既に説明したように、テレビは常に最新の映像データしか表示しません。ある場所でノーティフィケーションを待機が行われても、それ以前にノーティフィケーション送信で送られていた過去のデータは読み出すことができないのです。
例えば上記のプログラムを実際に動かすと、データ送信のボタンを押す間隔を長くする(ループ2の待機関数で指定した時間よりも長い間隔)と、ループ1もループ2も同じデータを受けとります。
しかし、送信のボタンを連打し短い間隔でどんどんデータを切り替えた場合、ループ速度が遅い(上記の例で言えばループ2)方はデータをとりこぼしてしまいます。
例えば上の図では、ループ1は送信データを全て反映し、0.642546、0.0379053、0.139453、0.781765、0.702603、0.702603の計6つのデータを受け取れているのに対し、ループ2は0.642546、0.139453、0.702603の3つしか受け取れていません。
実際のプログラムでわざとループの速度を遅らせることはないかもしれませんが、あるループの中の処理が重くて結果的にループ速度が遅くなることはあり得るため、上記のような取りこぼしが起きてしまう可能性がないかを考慮する必要があります。
キューとノーティファイアの違い
さて、改めてキューとノーティファイアの違いについてまとめてみます。
- キューは複数のデータを保存して一つずつ順番に取り出すため、過去にキューに入ったデータもその入った順番で取り出すことができるのでデータを失うことがないが、取り出した後はキューの中にデータが残らないため二つのループ間でしかデータの共有ができない
- ノーティファイアはデータを保存することがなく常に最新のデータしか共有できないものの、複数のループで同じデータを取り扱うことができる
どちらがより優れているといったことを考えるのではなく、「このときにはキューが適しているな」とか「これはノーティファイアが便利だな」と判断できるのが好ましいと思います。
例えば、とにかくデータの欠損があっては困るということであればキューを選ぶことになりますし、キューを複数使うことなく3つ以上のループでデータを共有したい(データを送信する側のループがそこまで高速で回らない場合など)ということであればノーティファイアを使用することになります。
データの欠損もなくしたいし3つ以上のループで値を共有したいという場合には・・・キューを複数用意することになるかと思います。
生産者・消費者デザインパターン
さて、ループ間でデータを受け渡す方法が二つあるということを紹介してきたのですが、実はキューの関数の仕組みを理解した時点で、有名なデザインパターンである「生産者・消費者デザインパターン」もおのずと理解できていることになります。
生産者・消費者デザインパターンのテンプレートを以下に紹介します。
・・・これはキューの関数を使ったループ間でのデータ受け渡しの例と何が違うの?と思った方、正解です。これはキューの使い方そのものです。
ループを二つに分けて、「片方のループでデータを取得し、もう片方のループで解析を行う」といった組み方をしています。これらを並列で実行させることで各ループがそれぞれの処理に専念し効率を上げる目的ですが、これらの処理それぞれで「データを取得=生産者」、「データを解析=消費者」になぞらえてこのような名前が使われています。
シンプルに考えると、例えば「1秒ごとに得られる新しいデータを取得しこの解析を行うのに1秒かかる」という処理を一つのループで行わせる場合、データの取得間隔は2秒おきとなってしまいます。なぜならループ一回には2秒(データ取得に1秒、処理にも1秒、これらが終わらないと次のループに進まないため)かかることになるからです。
一方で、それぞれのループにデータ取得、解析を行わせる生産者・消費者デザインパターンを使用している場合、データ取得のループは1秒ごと、解析のループも独立して1秒でそれぞれ回るためデータ取得間隔は1秒おきに行えます。
上記のような比較的簡単な例であればそこまで効率の違いを感じることはないと思いますが、それなりに規模が大きい場合だと顕著に効果が表れることがあります。
複雑さは増しても、「典型的な組み方」に沿って作られていれば、同じデザインパターンを知っている他の人が見てもプログラムの流れが追いやすいため内容を共有しやすいという利点もあります。
ステートマシンに加えて、こちらも比較的簡単に実装できながら強力なデザインパターンなのでキューの関数の使い方についてはよく慣れることをオススメします。
さて、ループ間でのデータの受け渡しについても一通り説明し終わった今、次のテーマとしてはプログラムを書く上で便利に使用できる仕組み、「機能的グローバル変数」を紹介しようと思います。
変数の多用は推奨されないと前回紹介しましたが、この機能的グローバル変数については様々な場面でよく使うことになる組み方なので、どんなものなのか仕組みを知っていきましょう。
もしよろしければ次の記事も見ていってもらえると嬉しいです。
ここまで読んでいただきありがとうございました。
コメント