From: Nakamura Minoru Subject: [Tech] Windows で Event を使う Date: Mon, 27 Aug 2001 22:49:06 +0900 中村 実っす。 ずいぶん昔に書きかけた物が出てきたので流すのです。 ============================================================================= POSIX Thread Library には mutex と呼ばれる同期オブジェクトが存在する。 mutex は通常、クリティカルセクションを保護するために使用される同期オブジェクト である。mutex のロック操作を複数のスレッドが行うと、そのうちの高々一つのスレッド のみがロックの所有権を獲得し、他のスレッドはサスペンドする。ロック所有スレッドが アンロック操作を行うと、ロック所有権が解放されて現在サスペンドしているスレッドの どれかが所有権を得てレジュームすることになる。 Thread1 Thread2 mutex_lock(m); mutex_lock(m); // critical section // critical section mutex_unlock(m); mutex_unlock(m); また POSIX Thread Library には condition variable と呼ばれる同期オブジェクトも 存在する。condition variable は、mutex と異なりサスペンドさせたいスレッドと、 サスペンドしているスレッドをレジュームするスレッドを非対称に設定できる。 そのため、スレッドが別のスレッドにイベントを通知するために使われるのことが多い。 Condition variable は cond_wait を実行したスレッド Thread1 がサスペンドし、 cond_signal を実行したスレッドがこれを起こすことになる。 Thread1 Thread2 cond_wait(c); cond_singal(c); だが実際にコードを書くときには、コンテキストによらず cond_wait を発効するような プログラムは作らない。普通は何らかの条件があって、それが満たされる時に cond_wait を実行することになる。同様に、cond_signal の発行にも一定の条件がある。 そこで、プログラムは以下のような形になる。 Thread1 Thread2 1: if( cond1 ){ 5: if( cond1 ){ 2: cond_wait(c); 6: cond_signal(c); 3: } 7: cond1 = false; 4: 8: } このプログラムには穴がある。 cond1 の条件があって 1 → 2 に処理が進む間に、cond1 の内容が変わる可能性がある。 ところがこのプログラムでは 1〜3、5〜8 がクリティカルセクションであるにもかかわらず 不可分処理になっていないのである。 そこで、クリティカルセクションに mutex ロックを導入してみる。  Thread1 Thread2 1. mutex_lock(m); 6: mutex_lock(m); 2: if( cond1 ){ 7: if( cond1 ){ 3: cond_wait(c); 8: cond_signal(c); 4: } 9: cond1 = false; 5: mutex_unlock(m); 10: } 11: mutex_unlock(m); しかしこれでは瞬く間にデッドロックを起こすプログラムとなる。 Thread1 が mutex ロック m を取得して 3 に進むと cond_wait のためサスペンドする。 Thread2 が cond_wait にシグナルを送るための処理を行おうとすると、mutex ロック m の ロック権が得られずデッドロックである。 ならば、3. で cond_wait を実行する前に mutex を unlock すればよいのである。 Thread1 1. mutex_lock(m); 2: if( cond1 ){ 3: mutex_unlock(m); 4: cond_wait(c); 5: mutex_lock(m); 6: } 7: mutex_unlock(m); これで一応、デッドロックの問題は解決された。 ところが、1〜7 の処理が不可分にならないという問題がまたぶり返してしまう。南無三。 ============================================================================= 解決編 上のような なんとももどかしい問題を 現在の POSIX は解決している。 それは cond_wait のフォーマットを見れば一目瞭然なのである。 int cond_wait(cond_t *c, mutex_t *m); cond_wait は一見不要に見える mutex へのポインタを第2引数に取る。 これは、 1. c によるサスペンドを行う前にロック状態にある m を解放する。 2. c にシグナルが送られ、再度 m のロック権を得られたらスレッドを ウェイクする。 1. と 2. はそれぞれ不可分な処理として実行される。 プログラムは下記のようにスマートに書き直されることになる。 Thread1 Thread2 1. mutex_lock(m); 6: mutex_lock(m); 2: if( cond1 ){ 7: if( cond1 ){ 3: cond_wait(c,m); 8: cond_signal(c); 4: } 9: cond1 = false; 5: mutex_unlock(m); 10: } 11: mutex_unlock(m); ============================================================================= 本題。 さて、POSIX Thread Library の mutex に相当する同期オブジェクトは、Win32API でも mutex と呼ばれる。Pthread の condition variable に相当するものは、Win32API では event と呼ばれている。 ところがこの event には「ロックされた mutex を解放しつつスレッドをサスペンドさせる」 手段が(つい最近まで)なかった。 そもそも、後発の OS である Windows は先人の失敗から何も学ばず、結局同じ落とし穴に落 ちているのである。一説では、Microsoft はその後この機能を提供するための API を準備した のだが、しばらく隠し API として公開しなかったようだ。 Microsoft もこれではさすがにまずいと思ったのか、WindowsNT4.0 からは SignalObjectAndWait と   よばれる API を導入して来た。そのため、NT 系では condition variable の全機能を実装すること が可能である(下記のプログラムを参照)。が、Win95/98 ではいまだ実装がされていない。 また、SignalObjectAndWait API の解説も難解で、問題意識を持たずにこれを読んだ人は、 このAPI が上記のような問題の解決のために使えるという点には気づかないであろう。 # 和書でこの SignalObjectAndWait に触れているスレッド解説書はないようだ。 ----------------------------------------------------------------------------------- // 同期オブジェクトの生成 hMutex = CreateMutex (NULL, FALSE, NULL); hEvent = CreateEvent (NULL, TRUE, FALSE, NULL); // スレッドの停止  WaitForSingleObject( hMutex, INFINITE ); if( cond ){ ResetEvent( hEvent ); SignalObjectAndWait( hMutex, hEvent, INFINITE, FALSE); } ReleaseMutex( hMutex ); // スレッド再開 WaitForSingleObject( hMutex, INFINITE ); SetEvent( hEvent ); ReleaseMutex( hMutex ); // 同期オブジェクトの消滅 ReleaseMutex( hEvent ); ReleaseMutex( hMutex ); ----------------------------------------------------------------------------------- 参考 ・SignalObjectAndWait  http://www.microsoft.com/japan/developer/library/jpwinpf/_win32_signalobjectandwait.htm ・Multiple Wait Semaphore http://www.aw.com/cseng/titles/0-201-63465-1/mltwtsm.htm ===================================================================================== From: Nakamura Minoru Subject: Re: [Tech] Windows で Event を使う Date: Tue, 28 Aug 2001 07:06:28 +0900 中村 実です。 > > At Mon, 27 Aug 2001 22:49:06 +0900, > Nakamura Minoru wrote: > > これってドキュメント読む限りはMFC上では使えないよう見えるんですが。 > #実際MFCを使った時はコンパイルすら通らない。 > その解決方法ってあるんでしょうかね? > でわ この関数は kernel32.lib にあります。 宣言は winbase.h にあって、_WIN32_WINNT が 0x400 以上に 定義されている時に有効になっています。 #if(_WIN32_WINNT >= 0x0400) WINBASEAPI DWORD WINAPI SignalObjectAndWait( HANDLE hObjectToSignal, HANDLE hObjectToWaitOn, DWORD dwMilliseconds, BOOL bAlertable ); #endif /* _WIN32_WINNT >= 0x0400 */ _WIN32_WINNT マクロの定義が MFC とぶつかるようでしたら、 SingleObjectAndWait の定義を直接手で書いてしまうという 手があります。 p.s こちらで実験したときは、MFC リンクしたときでも動くよう に見えました。