第89章 マルチスレッド その3


今回は、イベントオブジェクトについてやります。 イベントオブジェクトは、シグナル状態か 非シグナル状態になっています。イベントオブジェクトが 非シグナル状態の時、スレッドはWaitForSingleObject 関数のところで止まっています。そしてシグナル状態になると 制御を返します。 イベントオブジェクトを作るにはCreateEvent関数を使います。

HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, // セキュリティ属性WINDOWS95では無視 BOOL bManualReset, // FALSEに設定すると WaitForSingleObjectが制御を戻したときに //自動的に非シグナル状態になる BOOL bInitialState, // 初期状態 TRUE:シグナル状態 FALSE:非シグナル状態 LPCTSTR lpName // オブジェクトの名前 );

次にイベントオブジェクトをシグナル状態にするにはSetEvent関数を、非シグナル状態 にするにはResetEvent関数を使います。

BOOL SetEvent( HANDLE hEvent // イベントオブジェクトのハンドル );

成功したときはTRUE、失敗したときはFALSEを返します。

BOOL ResetEvent( HANDLE hEvent // イベントオブジェクトのハンドル );

成功したらTRUE、失敗したときはFALSEを返します。

DWORD WaitForSingleObject( HANDLE hHandle, // オブジェクトのハンドル DWORD dwMilliseconds // タイムアウト時間(ミリセカンド) );

オブジェクトがシグナル状態になるか、タイムアウトになるまで 待機します。dwMillisecondsにINFINITEを指定するとタイムアウトはなくなります。 (オブジェクトがシグナル状態になるまでずっと待機)

では、サンプルを見てみましょう。前章で作った プログラムをイベントオブジェクトをつかって、スレッドを制御します。 結構複雑になってしまったので、そのあらましを表にします。
ウィンドウプロシージャ動作
hWndWndProc
hChild1Child1ProcWM_LBUTTONDOWNでHitスレッドを動かす
--Hitスレッドが終了する直前にhChild1にWM_COUNTOKメッセージを送る
hChild1Child1ProcWM_CONUNTOKメッセージが来たらChild1スレッドを動かす
--Child1スレッドはhChildに描画が終わったらhWndにWM_COUNTOKメッセージを送る
hWndWndProcWM_COUNTOKメッセージが来たらhChild2にWM_COUNTOKメッセージを送る
hChild2Child2ProcWM_COUNTOKメッセージが来たらChild2スレッドを動かす

WM_COUNTOKメッセージは筆者が勝手に作ったメッセージです。 Child1, Child2スレッドは WaintForSingleObject関数で待機しています。変数NextOKが1の時、1番の子供ウィンドウがクリックされると Hitスレッドが作られて待機します。すぐにシグナル状態になってHitスレッドではcountが1増えます。 そのあと、NextOK=0にして一連の動作が終わるまで新たな左クリックを無視させます。 countを1増やすとhChild1にWM_COUNTOKメッセージを送ってスレッドを終了します。

Child1ProcではWM_COUNTOKメッセージが来たら、Child1スレッドに対してオブジェクトを シグナル状態にしてこれを動かします。

Child1スレッドではhChild1に対してcountの値を描画します。描画し終わったら 親ウィンドウにWM_COUNTOKメッセージを送ります。なぜ直接hChild2に送らないかというと このハンドルがわからないからです。hChild2をグローバル変数にすればよいのですが なるべくグローバル変数を使いたくないのでこのような面倒なことを行っています。

親のプロシージャではWM_COUNTOKメッセージが来たら、hChild2にWM_COUNTOKメッセージを 送ります。

Child2Procでは、WM_COUNTOKメッセージが来たらChild2スレッドを動かします。 Child2スレッドはcountの値をhChild2に描画します。そしてNextOK=1にします。

各スレッドは勝手に動けないのでNextOKに同時にアクセスが起こることは まずないと思われますが、念のためこれにアクセスするときは クリティカルセクションに入っています。

// mult03.cpp // Programmed by Y.Kumei #define STRICT #include <windows.h> #include <process.h> #define WM_COUNTOK WM_USER int NextOK = 1; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK ChildProc1(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK ChildProc2(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void Child1(void *); void Child2(void *); void Hit(void *); HWND CreateMyChild(HWND hWnd, WNDPROC ChildProc, LPSTR szChildName, char *); char szClassName[] = "mult03"; //ウィンドウクラス int count; typedef struct { HANDLE hEvent; HWND th_wnd; int th_end; }DATA, *PDATA; CRITICAL_SECTION cs; 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型に新たなメンバhEventが入っています。 また、自作のメッセージを作るときはその値をWM_USERから作っていきます。

#define WM_MYMESSAGE WM_USER #define WM_YOURMESSAGE (WM_USER + 1) #define WM_HERMESSAGE (WM_USER + 2)

という感じになります。

//ウィンドウ・クラスの登録 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 HWND hChild1, hChild2; LPSTR szChild1 = "child1", szChild2 = "child2"; RECT rc; switch (msg) { case WM_COUNTOK://Child1スレッドから SendMessage(hChild2, WM_COUNTOK, 0, 0); break; case WM_CREATE: InitializeCriticalSection(&cs); hChild1 = CreateMyChild(hWnd, ChildProc1, szChild1, "子供その1"); hChild2 = CreateMyChild(hWnd, ChildProc2, szChild2, "子供その2"); break; case WM_SIZE: if (IsIconic(hChild1)) OpenIcon(hChild1); if (IsIconic(hChild2)) OpenIcon(hChild2); GetClientRect(hWnd, &rc); MoveWindow(hChild1, 0, 0, (rc.right - rc.left) / 2, rc.bottom - rc.top, TRUE); MoveWindow(hChild2, (rc.right - rc.left) / 2, 0, (rc.right - rc.left) / 2, rc.bottom - rc.top, TRUE); break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: DeleteCriticalSection(&cs); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

Child1スレッドからWM_COUNTOKメッセージが来たら、 WM_COUNTOKメッセージをhChild2に送っています。

親が作られたらすぐに、InitializeCriticalSection(&cs);で クリティカルセクションを初期化して、親が終了するときに DeleteCriticalSection(&cs);でクリティカルセクションを削除しています。

HWND CreateMyChild(HWND hWnd, WNDPROC ChildProc, LPSTR szChildName, char *title) { HWND hChild; WNDCLASSEX wc; HINSTANCE hInst; hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = ChildProc; //プロシージャ名 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)szChildName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); RegisterClassEx(&wc); hChild = CreateWindow(szChildName, title, WS_CHILD | WS_VISIBLE | WS_THICKFRAME | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPSIBLINGS, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hWnd, (HMENU)100, hInst, NULL); return hChild; }

子供ウィンドウを作る関数で前回と全く同じです。

LRESULT CALLBACK ChildProc1(HWND hChild1, UINT msg, WPARAM wp, LPARAM lp) { static DATA data, data_hit; static HANDLE hEvent1, hEvent_Hit; switch (msg) { case WM_CREATE: hEvent1 = CreateEvent(NULL, FALSE, FALSE, NULL); hEvent_Hit = CreateEvent(NULL, FALSE, FALSE, NULL); data.hEvent = hEvent1; data.th_end = 0; data.th_wnd = hChild1; _beginthread(Child1, 0, &data); break; case WM_LBUTTONDOWN: EnterCriticalSection(&cs); if (NextOK == 0) { LeaveCriticalSection(&cs); MessageBeep(0xFFFFFFFF); return 0; } LeaveCriticalSection(&cs); data_hit.hEvent = hEvent_Hit; data_hit.th_wnd = hChild1; data_hit.th_end = 0; _beginthread(Hit, 0, &data_hit); EnterCriticalSection(&cs); NextOK = 0; LeaveCriticalSection(&cs); SetEvent(hEvent_Hit); //Hitスレッドを動かす break; case WM_COUNTOK: //Hitスレッドから来る SetEvent(hEvent1); //Child1スレッドを動かす break; case WM_DESTROY: data.th_end = 1; break; default: return (DefWindowProc(hChild1, msg, wp, lp)); } return 0; }

hChild2が作られたらすぐにhEvent1, hEvent_Hitを作ります。 そして、DATA型変数にhEvent1tをセットしてChild1スレッドに渡します。

左ボタンが押されたら、Hitスレッドを作り、動作させます。Hitスレッドは countを1増やすと終了するので、イベントで制御する必要はないのですが あえてこのようにしています。また、NextOK=0の時はHitスレッドを 作らずにビープをならしています。

BOOL MessageBeep( UINT uType // 音のタイプ );

uTypeに0xFFFFFFFFを指定すると、スタンダードビープになります。

HitスレッドからWM_COUNTOKメッセージが来たら、Child1スレッドを 動かします。

void Child1(void *data) { PDATA pEnd_data; char *str_org = "Count = %d"; char str[256]; HDC hdc; int j, k; long m = 1; static HWND hParent; pEnd_data = (PDATA)data; hParent = GetParent(pEnd_data->th_wnd); while (pEnd_data->th_end == 0) { WaitForSingleObject(pEnd_data->hEvent, INFINITE); for (j = 0; j <= 10000; j++) { for (k = 0; k <= 5000; k++) { m = m + j + k; } } wsprintf(str, str_org, count); hdc = GetDC(pEnd_data->th_wnd); TextOut(hdc, 10, 10, str, strlen(str)); DeleteDC(hdc); SendMessage(hParent, WM_COUNTOK, 0, 0);//親ウィンドウへ } _endthread(); return; }

Child1スレッドです。わざとに無駄な時間つぶしをしています。 描画が終わったら親にWM_COUNTOKメッセージを送っています。

void Hit(void *data) { PDATA pData; pData = (PDATA)data; WaitForSingleObject(pData->hEvent, INFINITE); count++; SendMessage(pData->th_wnd, WM_COUNTOK, 0, 0); //Child1ウィンドウへ _endthread(); return; }

Hitスレッドではcountを1増やしたらhChild1にWM_COUNTOKメッセージを送って スレッドを終了します。

LRESULT CALLBACK ChildProc2(HWND hChild2, UINT msg, WPARAM wp, LPARAM lp) { static DATA ch2_data; static HANDLE hEvent2; switch (msg) { case WM_CREATE: hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL); ch2_data.hEvent = hEvent2; ch2_data.th_end = 0; ch2_data.th_wnd = hChild2; _beginthread(Child2, 0, &ch2_data); break; case WM_COUNTOK: //親から SetEvent(hEvent2); //Child2スレッドを動かす break; case WM_DESTROY: ch2_data.th_end = 1; break; default: return (DefWindowProc(hChild2, msg, wp, lp)); } return 0; }

hChild2が作られたらすぐにhEvent2を作ってChild2スレッドを 待機させておきます。親からWM_COUNTメッセージが来たら Child2スレッドを動かします。

void Child2(void *data) { static PDATA pdata; HDC hdc; char str[256]; char * str_org = "Count = %d"; pdata = (PDATA)data; while(pdata->th_end == 0) { WaitForSingleObject(pdata->hEvent, INFINITE); wsprintf(str, str_org, count); hdc = GetDC(pdata->th_wnd); TextOut(hdc, 10, 10, str, strlen(str)); DeleteDC(hdc); EnterCriticalSection(&cs); NextOK = 1; LeaveCriticalSection(&cs); } _endthread(); return; }

Child2スレッドではcountの値をhChild2に描画してNextOK=1とします。 これでhChild2が左クリックされたときにHitスレッドを作ることが できるようになります。


今回作ったプログラムを動かしてみてください。1番の子供ウィンドウの クライアント領域を素早く連続してクリックしても1番、2番のウィンドウに 表示される数値に差ができません。また、番号が飛ぶということもなくなりました。 あまり速くクリックするとビープ音が鳴って無視されます。

しかし、今回作ったプログラムにもいろいろ問題があります。 そもそも_beginthread関数はWindowsのAPI関数ではなくCのランタイムです。 次回はもっと違う方法でスレッドを作ります。


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

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