こんばんわ、御世話になっております。
Cのロジックについて質問があります。
均等に3つの穴の開いた円盤があります。∴←(こんな感じでくりくり回ります)
スレッドA_downとA_upは棒を上げたり下げたりを行ないます。
スレッドB_turnは円盤の穴の位置が棒の下に来るように回転させます。
<条件>
円盤が回っているときに棒をおろすと棒が折れてしまい
棒が降りているときに円盤を回しても棒が折れてしまいます。
<ソース>
//棒と円盤の状態フラグを設定し制御する方法をとりました。
/////////////////////////////////
int flag_A = 0; //0:UP,1:Down
int flag_B = 0; //0:停止,1:回転中
A_down(){
while(flag_B != 0); //回転停止まで待つ
flag_A = 1; //棒が下がり始める前にフラグ設定
down(); //棒を下ろす命令
}
A_up(){
up(); //棒を上げる命令
flag_A = 0; //棒が上がりきったらフラグを設定
}
B_turn()
{
while(flag_A != 0); //回転させる前に棒が上がりきるまで待つ
flag_B = 1; //回転が始まる前にフラグを設定(回転中)
turn(); //回転開始命令
flag_B = 0; //回転が終わったらフラグ設定(停止中)
}
//////////////////////////////////
<問題点と質問>
降りる、下がる、回転するの3動作はどれが起こるか不定で
同時に起こる可能性もあります。
問題なのがA_downとB_turnのスレッドがほぼ同時に起動されたとき
flag_Aとflag_Bはともに0なので待ち判定を両方突破していきます。
結果として、回転が始まると同時に棒が降りてきます。
回転が速ければ衝突は避けられますが棒のほうが早いんです。
やはり排他制御が必要になるでしょうか?
スレッド排他制御は分かりにくいので
なんとかロジックでカバーする方法があればいいのですが・・・
分かりにくかった指摘お願いします!
はじめまして。
・スレッドをまたいで参照される変数はvolatileで宣言する。
この場合、flag_A、flag_Bとか。
・CRITICAL_SECTIONとかで排他処理をすればよいのでは
ないでしょうか。
へたなロジックを考えるより、排他を使ったほうが簡単ですよ。
ロジックで考えるのであれば、フラグではなくフェーズ管理をして一度手を
上げてから再度他のスレッドが手を上げていないかを確認してから処理を開始
するなんてのはどうですか。(他のスレッドが手を上げていたら初期状態へ戻
る。 かえって処理が遅くなるのでは・・・)
別の角度から考えてみました。
各スレッドの他に、実際に制御するスレッドを設けます。
(上下、回転動作はこの制御するスレッドでのみ行います。)
各スレッドは、制御用スレッドが動作処理中でない時に動作要求が発生したら
フラグをONします。(イベント処理でもOK)
制御用スレッドは、各フラグを参照して優先順位に従い、動作処理を実行する。
とこのようにしたら安心して動作処理を行えると思いますし、デバッグもしや
すいのではないでしょうか。
やはり排他制御を使用した方が良いさそうですね
ありがとうございます。
調べた結果をご報告致します。
クリティカルセクションを宣言して
スレッド内で以下のように記述しますと
EnterCrticalSection(&cl)とLeaveEnterCrticalSection(&cl)の間に
排他すべき処理を入れておけば(もちろん他のスレッドでも)
最初に入った方が終わるまで入るのを待ち続けるようOSが管理してくれると言うこと。
つまりすべてのスレッド(他の実行ファイルも含めて?)でクリティカルセクションに入れ
るのは1つのスレッドのみである。
で間違いないでしょうか・・・
自信なし・・
>別の角度から
なるほど・・・もう1つスレッドを作成して管理するわけですね。
デバッグのしやすさの利点も捨てがたいですね。
とりあえず当面はクリティカルセクションで制御してみたいと思います。
ありがとうございました。
ああ、失礼
>つまりすべてのスレッド(他の実行ファイルも含めて?)
すべてのスレッドというわけじゃないですね。
宣言したクリティカルセクションオブジェクトでただ1つが約束されている。
クリティカルセクションオブジェクトをいくつも作れば
それぞれでグループ化(表現悪いかな)した処理の排他を約束する。
こんなところでしょうか。
ぜんぜん違います?
同じ事が書いてるように見えますけど…。
> つまりすべてのスレッド(他の実行ファイルも含めて?)でクリティカルセクションに入れ
> るのは1つのスレッドのみである。
基本的にはこれで良いと思います。
他の実行ファイルも含めて排他制御したいときは、
名前付きMUTEXが利用できます。
> http://homepage2.nifty.com/xad/C/Group19/CriticalSection.htm
ここのコード例は、ちょっと間違ってますね。
局所変数としてCCriticalSectionを宣言してしまうと、
各スレッドからの関数呼び出しでそれぞれ別のCCriticalSectionが
出来てしまうので、排他制御されません。
>ぜんぜん違います
すみません、これは最初見ていたサイトに載っていた関数
EnterCrticalSectionというものを使わずにやっていたので
ぜんぜん違うと言う表現を使ってしまいました。
分かりにくい表現ですみません。
ところでひとつだけ気になるのですが
例えば円盤を回転させる処理で棒が降りていないことを確認するとします。
ここで棒が降りていたら棒があがるまで待つわけなんですが
棒をあげたと言うフラグもクリティカルセクションで設定しなければなりません。
つまり待つ処理がクリティカルセクションで待っていたら
当然デッドロックが発生し、止まってしまいます。
と、そこで・・・
待ち方を工夫してクリティカルセクションの開放取得をWhile文などで高速で繰り返し
他の待ちスレッドにも分けてやろうと思いました。
この状態でAスレッドがクリティカルセクション処理を実行中であるとします。
その間に、BスレッドとCスレッドがクリティカ要求を出しています。
次にAスレッドはクリティカルセクションを開放しますが
For文等で0.0001秒後ぐらい(とにかく一瞬後)にはまたクリティカルセクションを
要求しに来ます。
ここで私の要求したいのは待ち行列の処理です。
Aが終了->B開始-終了->C開始-終了->A開始
が確実に約束されているものなのでしょうか?
クリティカルセクションについてMSDNも参照したのですが
優先について詳しく載っているページを見つけることが出来ませんでした。
どなたかご存知の方がいらっしゃいましたらよろしくお願い致します。
あまり排他にこだわらずに、私の後の案(各スレッドの他に、実際に制御するスレッド
を設けます。)のように別の角度から考えられたほうが簡単ですよ。
この手の制御はよくシーケンサなどで処理されますが、負荷が重くなければシングルタ
スクのシングルスレッドでも問題なく動作させられると思います。
後は、共有のメモリエリアを利用して、フェーズ管理を行うとか。
1:上昇端停止中
下降か回転を許可する
下降の場合はフェーズ2へ
回転の場合はフェーズ5へ
2:下降中
上昇のみ許可する
下降端になったらフェーズ3へ
3:下降端停止中
上昇のみ許可する
上昇の場合はフェーズ4へ
4:上昇中
下降のみ許可する
上昇端になったらフェーズ1へ
5:上昇端回転中
回転停止のみ許可する
回転を停止したらフェーズ1へ
フェーズの確認から変更までの処理を排他処理する。
あまり詳しく書けませんでしたが、こんなもんでどうでしょう。