第92章 マルチスレッド その6


今回はミューテックスについてやります。ミューテックスは 一度に一つのスレッドのみが所有できます。 ミューテックスの作成はCreateMutex関数で行います。 不要になったミューテックスはCloseHandleで閉じます。 クリティカルセクションと似ていますが、このミューテックスは 他のプロセスからも使用できます。 今回も、あまりよいサンプルではありません。 二つのスレッドが親ウィンドウにランダムに書き込みを 行います。スレッド1は四角形を、スレッド2は楕円を 描画します。各スレッドはWaitForSingleObject関数で 待機しています。四角形を描画するスレッドが動いているときは 他のスレッドは眠っています。描画が終わったら ReleaseMutex関数でミューテックスの所有を解放します。

実際サンプルを作ってわかったことですが、ミューテックスを 使わないとやたら四角形が描画されて楕円はたまーーーにしか 描画されません。楕円は内部の計算が大変で時間がかかるのでしょう。 ミューテックスで排他制御するとほぼ、交互に描画されるようです。 つまり、時間のかかる楕円の描画中も四角形のスレッドは 全く眠っているからだと考えられます。 (このプログラムにミューテックスを使う意味があるか??という 疑問には目をつむって、ミューテックスの使い方だけを理解してください)

HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, // セキュリティ BOOL bInitialOwner, // イニシャルオーナー LPCTSTR lpName // ミューテックスオブジェクトの名前 );

この関数によりミューテックスオブジェクトを作ります。戻り値が ミューテックスハンドルとなります。

lpMutexAttributesは、Windows95では無視されます。

bInitialOwnerは、この関数を呼び出したスレッドにミューテックス オブジェクトを所有させるかどうかを指定します。TRUEならば所有します。

lpNameは、オブジェクトの名前を指定します。他のプロセス(アプリケーション) から、この名前を使ってミューテックスを所有させることができます。 (ハンドルは他のプロセスでは当然使えない)

BOOL ReleaseMutex( HANDLE hMutex //ミューテックスのハンドル );

ミューテックスの所有を解放します。成功するとTRUE、失敗するとFALSEを返します。

では、サンプルを見てみましょう。

// mult06.cpp #define STRICT #include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); DWORD WINAPI Thread1(LPVOID); DWORD WINAPI Thread2(LPVOID); char szClassName[] = "mult06"; //ウィンドウクラス typedef struct{ HWND hwnd; BOOL th_end; HANDLE hmutex; } DATA, *PDATA; int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }

ま、このへんは変わり映えしません。自作DATA型のメンバにhmutexが 加わった程度です。

//ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるマルチスレッド",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

ここもいつもと同じです。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static HANDLE hThread1, hThread2; DWORD threadID1, threadID2; static DATA data; static HANDLE hMutex; switch (msg) { case WM_CREATE: data.hwnd = hWnd; data.th_end = FALSE; data.hmutex = hMutex; hThread1 = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)Thread1, (LPVOID)&data, 0, (LPDWORD)&threadID1); hThread2 = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)Thread2, (LPVOID)&data, 0, (LPDWORD)&threadID2); hMutex = CreateMutex(NULL, FALSE, NULL); break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { data.th_end = TRUE; WaitForSingleObject(hThread1, INFINITE); MessageBox(hWnd, "スレッド1終了", "終了", MB_OK); if (CloseHandle(hThread1) != 0) MessageBox(hWnd, "hThread1クローズ成功", "成功", MB_OK); WaitForSingleObject(hThread2, INFINITE); MessageBox(hWnd, "スレッド2終了", "終了", MB_OK); if (CloseHandle(hThread2) != 0) MessageBox(hWnd, "hThread2クローズ成功", "成功", MB_OK); if (CloseHandle(hMutex) != 0) MessageBox(hWnd, "hMutexクローズ成功", "成功", MB_OK); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

親ウィンドウができたらすぐに2つのスレッドとミューテックスを作っています。 プログラムの終了通知が来たらdata.th_end = TRUEを実行してスレッドの終了を WaitForSingleObjectで待ちます。2つともスレッドが終了したらミューテックスを クローズしてプログラムを終了します。

DWORD WINAPI Thread1(LPVOID pparam) { PDATA pdata; HDC hdc; RECT rc; HBRUSH hBrush; int x1, x2, y1, y2, dx, dy, r, g, b; pdata = (PDATA)pparam; while (!pdata->th_end) { WaitForSingleObject(pdata->hmutex, INFINITE); GetClientRect(pdata->hwnd, &rc); dx = rc.right - rc.left; if (dx <= 0) dx = 1; dy = rc.bottom - rc.top; if (dy <= 0) dy = 1; x1 = rand() % dx; x2 = rand() % dx; y1 = rand() % dy; y2 = rand() % dy; r = rand() % 256; g = rand() % 256; b = rand() % 256; hdc = GetDC(pdata->hwnd); hBrush = CreateSolidBrush(RGB(r, g, b)); SelectObject(hdc, hBrush); Rectangle(hdc, x1, y1, x2, y2); ReleaseDC(pdata->hwnd, hdc); DeleteObject(hBrush); ReleaseMutex(pdata->hmutex); } return 0L; }

四角形を描画するスレッドです。1つ描画したらReleaseMutex関数で 所有を解放します。ところで、一般的にCのランタイムライブラリ中の malloc(), free(), やstdio.h, io.h,などに含まれる関数を 使うときはCreateThread関数を使わないのが無難であると言われています。 またこのサンプルのようにrand()を使う場合も良くないと言われています。 (このような場合は_beginthreadまたは、_beginthreadex関数を使う) もし、何か不都合が起こればCreateThreadを使うのをやめる必要があります。 幸い、このサンプルでは特別難しいことをしていないのでこれといった 不都合は起きていないようですが、_beginthread関数で書き直してみてください。

DWORD WINAPI Thread2(LPVOID pparam) { PDATA pdata; HDC hdc; RECT rc; HBRUSH hBrush; int x1, x2, y1, y2, dx, dy, r, g, b; pdata = (PDATA)pparam; while (!pdata->th_end) { WaitForSingleObject(pdata->hmutex, INFINITE); GetClientRect(pdata->hwnd, &rc); dx = rc.right - rc.left; if (dx <= 0) dx = 1; dy = rc.bottom - rc.top; if (dy <= 0) dy = 1; x1 = rand() % dx; x2 = rand() % dx; y1 = rand() % dy; y2 = rand() % dy; r = rand() % 256; g = rand() % 256; b = rand() % 256; hdc = GetDC(pdata->hwnd); hBrush = CreateSolidBrush(RGB(r, g, b)); SelectObject(hdc, hBrush); Ellipse(hdc, x1, y1, x2, y2); ReleaseDC(pdata->hwnd, hdc); DeleteObject(hBrush); ReleaseMutex(pdata->hmutex); } return 0L; }

さて、今回のサンプルはあんまり意味のないものでした。 次章では、もう少し実用に近づいたものを作ってみようと思います。

[SDK Index] [総合Index] [Previous Chapter] [Next Chapter]

Update Dec/07/1997 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。