ミューテックスによる排他制御処理

今までにスレッドの終了後にスレッドオブジェクトのハンドルを閉じることはやりました。
さて、マルチスレッドでのメモリ・フィイルへのアクセスの際に
同じメモリ領域に複数のスレッドで同時に書き込んだり、
あるスレッドから書き込むと同時に別スレッドで読み込んだりする時に
スレッド間での衝突・競合が発生するという問題が残っています。
(同時に読み出すだけなら問題ないようです・しかし読み出すだけなんて使えないですよね)
その競合などを解決するため、排他(同期)処理をとらなくてはなりません。
そこで、同期オブジェクトであるクリティカルセクション、ミューテックス、イベント、
メータードセクションといったものを使うことになりますが、(セマフォはCEではサポートされていない?)
今回はその中からミューテックスというものを使うことにします。

排他処理・・・あるスレッドがアクセスしている間、他のスレッドをそこにアクセスさせないようにロックする
同期処理・・・スレッドがある処理をしたことを確認してから別のスレッドを動作させるというもの

・ミューテックス (Mutex)

カーネルオブジェクトのミューテックスは、複数のスレッドが同時に同じ空間にアクセスするのを防ぐため
1つのスレッドからしかアクセスできないようにし、スレッドを同期化するための機能です。
(また、今回はやりませんがカーネルオブジェクトなのでマルチプロセスにも使えるようです)
具体的には、ミューテックスがあるスレッドに所有されたときに非シグナル状態になるので
別のスレッドは WaitForSingleObject でミューテックスがシグナル状態になるのを待ち、
ミューテックスを所有していたスレッドはそれを放棄してまた所有権が来るまで待ちます。
ミューテックスはシグナル状態になるので待っていたスレッドがミューテックスを所有します。
そしてなんらかの処理を行った後所有していたミューテックスを放棄します。
つまり、ミューテックスを所有出来るまで待ち、所有して処理を行いまた待ち・・・の繰り返しです。

では使い方を見ていきましょう。

// 各副スレッドのフラグ・FALSEなら終了
BOOL g_Flg_Thread1=TRUE;
BOOL g_Flg_Thread2=TRUE;
// ミューテックスのハンドル
HANDLE g_hMutex ;

     // ミューテックスの初期化
     g_hMutex = CreateMutex(0, FALSE, 0);

ミューテックスオブジェクトを作成します。
また、各スレッドのループ処理用フラグをグローバル変数で宣言します。

//==============================
//  スレッドファンクション1
DWORD WINAPI fncThread1 (LPVOID lpVparam) {

     WCHAR wcStr[64];
     int i=0;
     RECT Rect1={10,30,200,48};

     //(5000/2)回ループ
     while (g_Flg_Thread1)
          {
          //ミューテックスのアクセス権を待つ
          WaitForSingleObject(g_hMutex, INFINITE);
          i+=2;
          wsprintf(wcStr, _T("Thread1= %5d"),i);	
          DrawText(g_hdc, wcStr, -1, &Rect1, DT_LEFT);
          //他スレッドにアクセス権を渡す
          ReleaseMutex(g_hMutex);
     } //while

     wsprintf(wcStr, _T("Thread1=   END"));	
     DrawText(g_hdc, wcStr, -1, &Rect1, DT_LEFT);

     return 0;
}

スレッド1関数もスレッド2関数もほぼ同じなのでスレッド2の解説は省略しますが、
この関数の中で青字になっている部分が、whileのg_Flg_Thread1 という変数が
FALSE になるまでスレッド内でずっと繰り返される処理です。
WaitForSingleObject でミューテックスがシグナル状態になるまで待ち、
アクセス権を持ったらなんらかの処理を行い、その後
ReleaseMutex でミューテックスのアクセス権を放棄します。

          // 終了要求が出された
          case WM_CLOSE :
               //ミューテックスのアクセス権を待つ
               WaitForSingleObject(g_hMutex, INFINITE);
               //sleep()は、呼んだスレッド内でのみ有効であり
               //ミューテックスでアクセス権を占有してなければ
               //他のスレッドは動いていることになる
               Sleep(1000);
               g_Flg_Thread1=FALSE;
               g_Flg_Thread2=FALSE;
               //スレッドにアクセス権を渡す
               ReleaseMutex(g_hMutex);
               //スレッド1が終了するまで待つ
               WaitForSingleObject(hThread1, INFINITE);
               //スレッド2が終了するまで待つ
               WaitForSingleObject(hThread2, INFINITE);
               //スレッドオブジェクトのハンドルを閉じる
               CloseHandle(hThread1);
               CloseHandle(hThread2);

               //デバイスコンテクスト解放
               ReleaseDC(hwnd,g_hdc);
               break ;

さて、WM_CLOSEが呼ばれると終了処理のためにまずWaitForSingleObject で所有権を得ます。
Sleepで1秒メインスレッドを間止めていますが、これはメインスレッドにミューテックスがあるため
他のスレッドはミューテックス待ちで動作していないのを確認するためです。
そしてg_Flg_Thread1・g_Flg_Thread2 をFALSE にし、他のスレッドにアクセス権が渡った時
whileループを抜け「END」を表示して終了するようにします。
そして2つのサブスレッドがそれぞれ終了(シグナル)状態になるまでWaitForSingleObject で待ち
CloseHandle でスレッドオブジェクトを閉じてプロセスを終了します。

なお、ミューテクスもプロセス終了時にシステムが自動的にハンドルをクローズするので
オープンしたミューテックスはCloseHandle しなくてもシステムにより破棄されます。

では以上を踏まえたソースを実行してみましょう。

ミューテックスの排他制御処理

スレッドの動作は制御されているため、競合問題は解決され安全に動作します。
またプログラムが終了する前に、スレッドを自分の思ったところで終了させてから
終了出来るようになりました。とりあえずこれでスレッドの基本の使い方がやっと終わりました。
応用は・・・知りません。またいつか。

HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName );
・名前付きまたは名前なしのミューテックス(相互排他)オブジェクトを作成または開きます
第一引数 lpMutexAttributes には、セキュリティ記述子を指定します
第二引数 bInitialOwner には、ミューテックスオブジェクトの初期の所有者を指定します
第二引数 lpName には、ミューテックスオブジェクトの名前を保持している、NULL で終わる文字列へのポインタを指定します

戻り値 成功ならミューテックスオブジェクトのハンドルが、失敗ならNULL が返ります。
 この関数を呼び出す以前にそのミューテックスオブジェクトが存在していた場合は、
 この関数は既存のオブジェクトに対するハンドルに返し、GetLastError 関数は
 ERROR_ALREADY_EXISTS を返します。それ以外の場合、指定されたミューテックスを作成します。

BOOL ReleaseMutex( HANDLE hMutex );
・指定されたミューテックス(相互排他)オブジェクトの所有権を解放します
第一引数 hMutex には、ミューテックスオブジェクトのハンドルを指定します (in)

戻り値 成功ならTRUE(0以外の値) が、失敗ならFALSE(0) が返ります。

ソースの表示

2005/7/10


戻る