第88章 マルチスレッド その2


今回は、スレッドの同期の問題に付いてやります。 どんなときに問題になるかというと、複数のスレッドで 共通の変数に対して読み書きを行った場合です。 スレッドの実行される順番は予測が付きません。 従ってあるスレッドAで共通の変数に対して何かの操作を加えている時に 別のスレッドBでこの変数にたいして、別の操作が加わるということが 起こり得ます。運が悪いとハングアップするかもしれません。


今回は子供ウィンドウを2つ作り、最初のウィンドウの クライアント領域をクリックするとそのクリックされた回数を 2つの子供ウィンドウに表示するプログラムを作ります。 それぞれの子供ウィンドウから副スレッドを作ります。 このスレッドはプログラム終了まで存在します。 そして、グローバル変数のカウントを監視して それをクライアント領域に表示します。 また、一方のスレッドにはわざとに時間のかかる無駄な仕事を させます。 子供ウィンドウがクリックされるとまた別の副スレッドが生成されて カウントを1増やしてこのスレッドは終了します。
この場合、両方の子供ウィンドウに表示される数字は必ずしも 一致しません。特に素早くクリックした場合によくわかります。

とりあえず、両方の子供ウィンドウに表示される数字をなるべく 一致させたい場合どうすればよいか?という問題になります。

これには、クリティカルセクションというものを使います。 まず、クリティカルセクションオブジェクトを定義します。

CRITICAL_SECTION cs;

というように定義します。次にこれを初期化します。

VOID InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection //クリティカルセクションオブジェクトのアドレス );

これで、クリティカルセクションオブジェクトの初期化ができました。 不要になったら

VOID DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

で廃棄します。 スレッドでは、

VOID EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

でクリティカルセクションオブジェクトを所有することができます。 (クリティカルセクションに入る) クリティカルセクションオブジェクトは1度に1つのスレッドしか 所有することができません。

VOID LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

でクリティカルセクションから去ります。 (他のスレッドがクリティカルセクションオプジェクトを所有できる)

これで、少なくとも同じ変数に対して複数のスレッドからアクセスされる ことを防ぐことができます。

// mult02.cpp // Programmed by Y.Kumei #define STRICT #include <windows.h> #include <process.h> 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[] = "mult02"; //ウィンドウクラス int count; typedef struct { 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; }

クリティカルセクションを使わないとint conutに複数の スレッドから同時にアクセスが起きる可能性があります。

//ウィンドウ・クラスの登録 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_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; }

WM_CREATEですぐにクリティカルセクションを初期化します。
このあと、子供ウィンドウを2つ作ります。

親ウィンドウのサイズが変えられたら(WM_SIZE)、子供ウィンドウを 親のクライアント領域の半分の大きさにして、左右に位置させます。 毎度おなじみのMoveWindow関数を使っていますが、この関数は ウィンドウがアイコン化されていると、ウィンドウの大きさ指定 の引数は無視されます。そこで、この関数を実行する前にIsIconic 関数でアイコン化されているかどうかチェックをします。 もしアイコン化されていたらOpenIcon関数で元の大きさに戻します。

BOOL IsIconic( HWND hWnd );

アイコン化されているとTRUEを返します。そうでないときはFALSを返します。

BOOL OpenIcon( HWND hWnd );

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

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; switch (msg) { case WM_CREATE: data.th_end = 0; data.th_wnd = hChild1; _beginthread(Child1, 0, &data); break; case WM_LBUTTONDOWN: _beginthread(Hit, 0, 0); break; case WM_DESTROY: data.th_end = 1; break; default: return (DefWindowProc(hChild1, msg, wp, lp)); } return 0; }

第1の子供ウィンドウのプロシージャです。1番の子供が作られると すぐにスレッドを作ります。

このウィンドウが左クリックされると、また別のスレッドが作成されます。

ウィンドウが破壊されるときにスレッドも終了させます。

void Child1(void *data) { PDATA pEnd_data; char *str_org = "Count = %d"; char str[256]; HDC hdc; int j, k; long m = 1; pEnd_data = (PDATA)data; while (pEnd_data->th_end == 0) { EnterCriticalSection(&cs); 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); LeaveCriticalSection(&cs); } _endthread(); return; }

whileループにはいるとすぐに、クリティカルセクションを 所有します。そして、countを調べてクライアント領域に表示します。 その前に、むだな時間つぶしをしています。 時間つぶしをしている間中クリティカルセクションに入っていますので 他のスレッドがどんどん先の数値を表示していくということは 防げるはずです。(もちろんこのようなことをすると全体の パフォーマンスが落ちます) 表示か終わったらクリティカルセクションを去ります。

void Hit(void *data) { EnterCriticalSection(&cs); count++; LeaveCriticalSection(&cs); _endthread(); return; }

最初の子供ウィンドウが左クリックされたときに 作られたスレッドです。カウントを1つ増やしているときのみ クリティカルセクションに入っています。そして すぐにスレッドを終了します。

LRESULT CALLBACK ChildProc2(HWND hChild2, UINT msg, WPARAM wp, LPARAM lp) { static DATA ch2_data; switch (msg) { case WM_CREATE: ch2_data.th_end = 0; ch2_data.th_wnd = hChild2; _beginthread(Child2, 0, &ch2_data); break; case WM_DESTROY: ch2_data.th_end = 1; break; default: return (DefWindowProc(hChild2, msg, wp, lp)); } return 0; }

2番目の子供ウィンドウのプロシージャです。

ウィンドウが作られるとすぐにスレッドを作っています。 また、ウィンドウが破壊されるときにスレッドを終了させています。

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) { EnterCriticalSection(&cs); wsprintf(str, str_org, count); hdc = GetDC(pdata->th_wnd); TextOut(hdc, 10, 10, str, strlen(str)); DeleteDC(hdc); LeaveCriticalSection(&cs); } _endthread(); return; }

2番目のウィンドウによって作られたスレッドです。 カウントを表示している間クリティカルセクションに 入っています。

さて、この例題のプログラムは余りよい見本ではありませんが、 クリティカルセクションを使ったときと使わなかったときの プログラムの動作を確認してみてください。また、 EnterCriticalSection, LeaveCriticaSection関数の 位置を変えて動作を確認してみてください。 共通変数に対するスレッドの同時アクセスを防ぐ意味から だけなら、共通変数にアクセスするところをこの関数で サンドイッチすればよいことになります。 今回は2つのウィンドウに表示される数値をなるべく一致させる目的で 使っていますので、多めにサンドイッチしています。 特に、無駄な時間つぶしをしている間クリティカルセクションに 入っているスレッドがあります。
実際に動かしてみればわかりますが、2つのウィンドウに表示される 数値にはずれがあります。また、素早くクリックすると数値が 飛ぶことがあります。


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

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