この記事は、初心者向けのまずこれシリーズ第23、24回の補足記事です。キューやノーティファイア以外に同期のパレットにある他の関数の役割について知りたい、という方向けにもう少し内容を補足しています。
同期につかう他の関数たち
まずこれのシリーズでは、複数のループ間でデータをやり取りするための方法としてキューやノーティファイアを紹介しました。
これらは関数パレットの同期というパレットの中に入っているものです。
同期パレットの中には、これら以外にも、セマフォ、ランデブー、オカーレンスといった項目が並んでいます。
キューやノーティファイアもそうでしたが、ここでいう「同期」とは、複数のループ(多くの場合Whileループ)どうしの状態を制御する操作のことを指しています。
これらはいつどのように使うんだ、ということで、これら3つの機能を簡単に紹介していきます。
基本的にサンプルファインダのサンプルをベースに説明しますが、これをベースに修正した例も挙げていきます。
サンプルは、サンプルファインダの「アプリケーションの最適化」にある「タスクの同期」中に入っています。
セマフォの役割
まずはセマフォです。
これは、一言で言い表すなら、「ある瞬間に実行される処理の数を制限したいときに使用」するものです。
「セマフォ」の単語の意味としては、排他制御の際に、指定された個数だけ共通のリソースへ同時アクセスできる数のことを表わすようです。
何のこっちゃよくわからない、と思うので、サンプルを見てみます。セマフォには、「簡易セマフォ」というサンプルがあります。
こちらのプログラムでは二つのWhileループがあり、実行するとデータと書かれた二つのチャートの内どちらか一方だけが更新され、データが10個出た時点で今度はもう片方のチャートのみが動く、という仕組みになっています。
このサンプルプログラムを改造せずそのまま動かした場合、「セマフォ」のアクセス権は1となっています。
このアクセス権を、二つのWhileループのうちどちらかが取得しています。例えばループ1にアクセス権がある間(アクセス権を解放するまで)は、もう片方のループ2ではセマフォ取得はできません。
取得ができないということは、セマフォ取得の関数が実行できないということになり、つまりループ2はセマフォ取得の関数で足止めを食らうことになります。そのためループ2はチャートへデータを表示することができず、フロントパネルではループ1にあるチャートしか更新されない状態となります。
ループ1がセマフォを解放すると、待ってましたとばかりにすぐさまループ2がセマフォを取得するため、今度はループ1が待ちぼうけを食らいます。
以降、この交互のセマフォ取得が続きます。
アクセス権の数は、セマフォリファレンス取得の関数で変更することが可能です。何も配線されていないと1なのですが、例えばここを2とすると、このサンプルの場合にはどちらのWhileループも動くことになります。
なので例えば、Whileループをもう一つ増やして合計3つにして、それでセマフォのサイズを2とすると、3つのWhileループの内2つは常に動けますが残りはどれかがセマフォを解放しないと動けない状態となります。
このように、同じセマフォリファレンスでつながれた「セマフォ取得」の関数は、「セマフォリファレンス取得」で指定されたサイズの数の分だけしか実行できず、セマフォ解放によってアクセス権に空きが出ない限りループも回らない、ということになります。
セマフォの関数に信号機の絵が描いてあるのは、こうした複数のループ(道路)の実行(車の移動)を制限(交通整理)していると考えるとわかりやすいと思います。
ランデブー
続いてランデブーです。
これも一言で言い表すなら「複数の処理の実行開始タイミングを揃える」ために使用します。
単語の意味としては「待ち合わせ」という意味があるようですが、まさにこの言葉通りの動作をします。
これまた「簡易ランデブー」というサンプルを例に動作を紹介します。
このサンプル、実行すると、二つのチャートに同じタイミングでプロットがされ始めます。Whileループ1回の間に一度にプロットされる数は「合計1」や「合計2」に表示されています。
このとき、合計のうち数字が小さい方のチャートは先にプロットが終了しそれ以降は待機状態となります。一方で数字が大きい方は引き続きチャートへのプロットが続くのですが、合計の数だけプロットされ終わると再び両方のチャートが表示されます。
この仕組みは「ランデブー待機の部分で一旦集合して、集まったらみんな一斉にスタートする」という動作で説明できます。
そのため、複数のループがあったとして、どれかが他に比べて極端に早くなったり遅くなったりすることを防ぐことができます。
サンプルだとループの待ち時間が定まっていないので効果が分かりづらいかもしれませんが、待ち時間を固定にするとその様子がもう少しはっきりすると思います。
せっかくなのでループを増やした場合の例として紹介します。
上の例でも、Forループの回数を各ループばらばらにすることでそれぞれのWhileループの一回の実行が終わるタイミングをわざとずらしていますが、Forループの実行回数は同じにして待機の関数の値を変更したほうが、より現実的にランデブーの関数を使用する場面に近いかもしれません。
オカーレンス
最後にオカーレンスです。が、実はこれについては、基本的にはノーティファイアで置き換えられてしまうものになっています。
一言で表すなら「データを渡さないノーティファイア」です。
ヘルプページを見ても「ナショナルインスツルメンツでは、ほぼすべての操作のオカーレンスに「ノーティファイア操作関数」を使用することをお勧めします。」と書いてあり、じゃあ何でオカーレンスがあるの?と思いたくなるくらい、存在意義が疑われる関数です。(もちろん存在意義はあるにはあるのですが、通常の書き方というか用途ではあまり必要とされません)
例にもれず、サンプルファインダの簡易オカーレンスのプログラムで動作を確認してみます。
このプログラムでは上のWhileループの中にケースストラクチャがあり、10回に一回オカーレンス大気の関数が実行されるようになっているのが確認できます。
プログラムを実行すると、フロントパネルの数値表示器の値が変わるのですが、「発行されたオカーレンスの合計」の値は、「現在のループ1の反復数」が10変わる毎に1ずつ増えることが確認できます。
動作としては、片方のループで「オカーレンス設定」が実行されたときだけもう片方のループの「オカーレンス待機」が実行されるようになる、という仕組みです。
「あるループで特定の動作が実行された場合のみ他のループの実行を行わせる」という場合に使える、ということになります。
ただ・・・これはノーティファイアで置換できてしまいますし、何ならノーティファイアの方が使い勝手がいいというケースが多いです。
なぜなら、ノーティファイアの場合には「ノーティファイア取得」で指定したデータタイプのデータを「ノーティフィケーション送信」から「ノーティフィケーション待機」に渡すことができるためです。
送信時点では必ず何かしらデータを送信する必要がありますが、待機の方では必ずしもそのデータを使用する必要はありません。
オカーレンスのサンプルをノーティファイアの関数で置き換えた場合の例を以下に紹介します。
何かしら理由がない限りはあまり使われない関数ではあるものの、なんとなくこういう使い方なんだな、くらいに頭の片隅にでも入れておけばいいと思います。
同期パレットにある、セマフォ、ランデブー、オカーレンスの機能について紹介しました。どれも複数のループについて、互いのループ処理のタイミングの相対的な関係性を制御するために使われることがなんとなくつかんでもらえたかなと思います。
実際問題、どのような場面で使用するか、すぐには思いつかないかもしれませんが、プログラムを書いていく中で「この処理とこの処理は同時に起こってほしくないな」とか「この処理とあの処理の開始タイミングは常に一緒がいいな」、「この処理が終わってからあの処理が実行されるようにしたいな」という場面が出てくると思います。
そんなときに機能として同期パレットの中の関数が使用できるんだな、ということが頭の片隅に入っているだけでも、機能を知らなくて実装ができず迷ってしまう時間を減らせることができると思います。
ここまで読んでいただきありがとうございました。
コメント