並列ループの止め方 | マーブルルール

並列ループの止め方

ステップアップ

スポンサーリンク

この記事は、初心者向けのまずこれシリーズ第23、24回の補足記事です。

ループ間でデータを受け渡す方法はわかっても、ループを止める方法がわからないと並列ループを扱うプログラムをうまく組めないことがあります。

そのため、一つのループが止まったら関連する動作をするループも同時に止まるといった仕組みの実装方法をいくつか知っておくのが大切です。

スポンサーリンク

いつでも一方向から止めるとは限らない

キューやノーティファイアを使用することでループ間でデータを受け渡すことができるということはまずこれシリーズの第23、24回の記事で紹介した通りです。

キューは1対1でデータをやり取りするため、通常2つのループ間でデータを受け渡す場合に使用し、その受け渡す対象となるデータはためておくことができるため、データの欠損が起きにくいのが特徴でした。

(キューの使い方次第では、ロッシーの関数の使用により生産者ループで得られているデータすべてを消費者側に送れない場合があるため、全ての場合においてデータの欠損が起きない、というわけではありません)

ノーティファイアはデータをためることができず常に最新のデータの受け渡ししかできないのですが、1対複数でのデータ受け渡しができるため、キューとはまた違った使い方でループ間のデータ共有ができる、というものです。

どちらもループ間でデータの受け渡しをするのには大切ですが、同じくらい大切なのが、これらの仕組みが使われているプログラムでのループの止め方です。

例えば、よくあるループの止め方の一つは、「エラーを利用する」ことです。

上で紹介したプログラムでもそのように実装していますが、他の方法とも比べて紹介するために、一つ簡単なプログラムを例にとってみてみます。

更新ボタンが押されたら数値(乱数)が数値表示器に出てくる、というプログラムを考えたとします。

このプログラムを生産者消費者のキューを使用したデザインで書いた場合、ループが2つあるのはいいとして、一つしかない停止ボタンは通常生産者側ループの停止条件に割り当てることが多いと思います。

もう一つの、消費者側のループの停止には、デキューの関数のエラー出力を配線し、このデキュー関数のエラーによってループを止める、という書き方にします。

エラーが出るのは、生産者側ループが終了してからその後に実行されるキュー解放の関数が実行されることでキューそのものがなくなることが原因です。

ほとんどの場合エラーは出ない方がいいと考えますが、このように意図的にエラーを出してこれを元にループを止めるという方法は便利なことがあります。

以下の図がその一例です(消費者側の内側のケースストラクチャには「Run」と書かれたケースがありますが、これ以外はデフォルトケースしかないとします)。

ただし、多くの例では、この止め方は「生産者消費者」でいうところの「消費者」の止め方になっています。

生産者の止め方は、プログラムにも依りますが、ユーザーが停止ボタンを押した時と組むことが多いかと思います。

つまり、形としては、生産者ループが止まったその流れで、消費者ループが止まるという順番になることが多いです。

ただ、プログラムによっては、消費者側でエラーが起こり、そのエラーが起こったらプログラムを止めたい、といった場面が起こりえます。

つまり、消費者ループから生産者ループを止める、ということですね。

例えば、プログラムを少し改造して、わざと消費者ループでエラー(キュー解放の関数の実行によるものでない)が発生するようにしたとします。

ほとんどの場合こうなってしまうと生産者ループを「自動的に(=プログラム的に)」止めたくなると思いますが、ではどうやって止めるのか?

停止ボタンを押すというのはユーザーの操作を伴うので「自動的に」ではないですね。

こんな場合にどうすればいいか、ですが、いくつかの実装方法を紹介します。

ループの止め方色々

単純な生産者消費者ループの場合

まずは、消費者ループの終了を判定するブール表示器を用意して、これを生産者ループの停止条件の判断材料にする方法です。

実装としては一番シンプルな形になります。

例えば以下のような形が考えられます。

ただ、これだけのためにブール表示器を用意したりローカル変数を使うというのは、あまりプロっぽくありません(簡単なプログラムであれば重宝する使い方だとは思いますが)。

あるいは、ノーティファイアを使用する、といった実装もありえます。

少し変わった実装に見えますが、ノーティフィケーション待機の関数のタイムアウトに0を入力させれば、普段はノーティフィケーションの関数がないときと同じように動作させられ、ノーティフィケーションが送信されたときだけ特定の処理を実行させることができます。

以下の図では、ノーティフィケーション送信は消費者ループを抜けた後に実行されるようになっていますが、消費者ループの中の外側のケースストラクチャ(「エラーなし」となっているもの)の「エラー」ケース内に送信の関数を置いても同じことです。

あるいは、ノーティフィケーション送信の関数を消費者ループの中に入れておいて(ケースストラクチャの外)、エラーがない場合にはFalseを送信、エラーがあったらTrueを送信、といった形でも実装できるかと思います。

上の例ではノーティファイアで扱うデータタイプに指定しているのはブール値ですが、例えば列挙体あるいは文字列を指定して、消費者側でエラーが起こった際に生産者側で特定の処理を実行できるように工夫するといった使い方もできます。

イベントストラクチャが含まれている場合

これらの方法でどんな時でも対応できるじゃん、と思うかもしれませんが、イベントストラクチャが絡んでくると話が少し違ってきます。

生産者消費者のデザインでイベントストラクチャが関わってくるのは基本的に生産者側ですが、イベントストラクチャはイベントが発生しないと処理されないため、もし消費者側でエラーが起きて生産者側を止めたくなっても、何らかの方法でイベントを発生させる必要があります。

イベントストラクチャはユーザーの操作を前提としたイベントが多いため、それだけだとユーザーによる特定の操作がないとイベントストラクチャが検出しないことになりますが、本来はプログラム的にイベントを発生させて止められるような仕組みになっている方が安全安心です。

知っている人は、プロパティノードを使って「値(信号)」プロパティによって強制的にイベントを発生させこれを条件に生産者ループを止める方法を思いつくかもしれませんが、個人的にこの方法はオススメしません。

知らない方のために一応簡単に紹介するため、イベントを発生させるボタンをフロントパネルに配置したとします。

そしてブロックダイアグラムで、このボタンが押されたらプロパティノードの「値(信号)」に何かしら値が入るような仕組みにしておきます。

似たプロパティ項目で「値」というものがありますが、「値」の変更はイベントとして検出されないのに対し「値(信号)」は値変更イベントで検出される、というのが違いになります。

少なくとも、そこそこの規模のプログラムでこういった止め方をしているプログラムは見たことがないですし、大きなプログラムになるほど「え、どこでどうやって止めているの(どこに「値(信号)」が使われているの)?」ということがわかりにくいのは、プログラム全体の見通しの悪さにつながります。

プロパティノードは、独立して(他のワイヤ配線をされずに)単独で存在させることができるため、「ワイヤをたどってデータフローを知る」ことができるLabVIEWの利点を損なってしまいがちになります。

また、イベントストラクチャにタイムアウトイベントを設けて、意図的に一定間隔でこのイベントを起こすことでループを止めるという方法もありますが、他に理由があってタイムアウトイベントを設けているわけではないのであれば、普段は無駄となってしまうイベントが定期的に実行されるような仕組みは最適解とは言えないかなとも思います。

それよりは、「あるある」な形でイベントを止めるような実装にしておけば、プログラムを見た瞬間にイベントストラクチャの止め方はこれなんだなという判断がつくため、全体の構造も把握しやすくなります。

その「あるある」の方法がユーザイベントによる止め方で、基本的な構造は以下の図のような仕組みになっています。

ユーザイベントとは、プログラム的にイベントを起こさせるための仕組みで、ユーザイベントを作成した後にプログラムのどこかでそのイベントを生成すれば、イベントストラクチャでこれを検出できる、というものです。

ユーザイベントを検出する際には、「ダイナミックイベント端子」に、イベント登録refnumを配線する必要があります。

ダイナミックイベント端子はイベントストラクチャを右クリックして選択しないと、デフォルトでは表示されないので注意してください。

また、ユーザイベントを作成の関数に渡すユーザイベントデータタイプのデータは必ずラベルが必要になります(このラベルがイベントストラクチャ上でのイベント名の基になります)。

イベント生成のタイミングは、消費者ループでエラーが起きたタイミングでもいいですし、停止ボタンが押されたら一度生産者ループから消費者ループを止めるケースに入り、そこから生産者ループに戻るといった組み方が有効な場合があります。

もう少しユーザーイベントのことが知りたいよ、という方は以下の記事が参考になるかもしれません。

ノーティファイアを使用したループの止め方

キューを使用したプログラムばかりでもないと思うので、ノーティファイアを使用している場合のプログラムについても考えてみます。

・・・とはいっても、キューを使用した場合のループの止め方と同じような考えが適用できるので、キューとノーティファイアで別個のものとして考える必要はあまりないため、追加で紹介することは特にありません。

ただ、キューのところでノーティファイアを使用したループの止め方を紹介していましたが、ノーティファイアは複数のループを止めることに適していて、例えば以下のような実装があります。

複数のループに同時に同じ命令を送るといった操作はキューにはできない操作なので、プログラムによってはノーティファイアが特に便利に使えるケースがあると思います。

本記事では、並列ループの止め方について紹介しました。

LabVIEWの特徴である、簡単に並列化した処理が書けるという利点に絡めてその止め方にまで気を配って全体の設計を考える際に本記事が役に立てばうれしいです。

ここまで読んでいただきありがとうございました。

コメント

タイトルとURLをコピーしました