・排他処理に関する注意点(2000/1/21)

排他処理を行う場合はクリティカルセクションや セマフォを使うというのが常套手段ですが、なんでもかんでも クリティカルセクションやセマフォを使えば良いって訳ではありません。 例えば次のソースを見てください

// 排他処理のテスト
// (c) tokai@fides.dti.ne.jp  1/21/2000
// リソースエディタで IDD_DIALOG というダイアログを作っておく

#include <windows.h>
#include "resource.h"

LRESULT CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

CRITICAL_SECTION csec;
HANDLE hSemaphore;

//-------------------------------------------------------------------
// メイン
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPreInst,
   LPSTR lpszCmdLine, int nCmdShow){

   // セマフォ初期化
   hSemaphore = CreateSemaphore(NULL,1,1,NULL);

   // クリティカルセクション初期化
   InitializeCriticalSection(&csec);

   DialogBox(hInst,MAKEINTRESOURCE(IDD_DIALOG),NULL,(DLGPROC)DlgProc);

   // セマフォ開放
   CloseHandle(hSemaphore);

   // クリティカルセクション開放
   DeleteCriticalSection(&csec);

   
   return 0;
}



//-------------------------------------------------------------------
// ダイアログプロシージャ
LRESULT CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{

static int counter;

    switch (msg) {

    case WM_INITDIALOG: 

    SetTimer(hWnd,1,100,NULL);

    break;
    
    case WM_TIMER:

    if(++counter < 6) MessageBox(hWnd,"テスト","",MB_OK);

    break;

    case WM_CLOSE: 

    KillTimer(hWnd,1);
    EndDialog(hWnd,IDOK); 
    
    break;
    
    default:
    return FALSE;
    }
    return TRUE;
}

これを実行すると「テスト」と書かれたメッセージボックスが間髪おかずにパッパッパ と表示されるはずです。では前のメッセージボックスを閉じない限り次のメッセージボックス が表示されないようするにはどうすれば良いでしょうか。

最初にクリティカルセクションを使って WM_TIMER の所を次の様にしてみます。

EnterCriticalSection(&csec);
if(++counter < 6) MessageBox(hWnd,"テスト","",MB_OK);
LeaveCriticalSection(&csec);

ところがこれを実行してもまったく以前と同じでパッパッパ と表示されます。その理由は MSDN の EnterCriticalSection() の項によると

「Once a thread has ownership of a critical section, it can make additional calls to EnterCriticalSection or TryEnterCriticalSection without blocking its execution. This prevents a thread from deadlocking itself while waiting for a critical section that it already owns.」

だからです。といってセマフォの方を使って

WaitForSingleObject(hSemaphore,INFINITE);
if(++counter < 6) MessageBox(hWnd,"テスト","",MB_OK);
ReleaseSemaphore(hSemaphore,1,NULL);

として実行しては「いけません」。deadlock します(笑)

セマフォを使って望むような処理をしたい場合は次の様にします。

if(WaitForSingleObject(hSemaphore,1) == WAIT_TIMEOUT) break;
if(++counter < 6) MessageBox(hWnd,"テスト","",MB_OK);
ReleaseSemaphore(hSemaphore,1,NULL);

これで前のメッセージボックスを閉じない限り次のメッセージボックスは 現れなくなります。

まあ、今回のような処理ならばわざわざセマフォを使わなくてももっと良い 方法があるような気がしますが、要するにクリティカルセクションや セマフォを過信してはいけないって事ですね。