スレッドの待機
スレッドの一時停止
Sleep関数
スレッドは(メインスレッドを含めて)、コンピュータの処理能力の許す限り高速で処理を行います。
通常はそれで良いのですが、多少時間はかかっても良いのであまりCPU負荷を掛けたくない場合もあります。
その他、アニメーションを行う場合は速度を調整しなければ速すぎてまともに再生できないでしょう。
そのような場合はSleep
関数でスレッドを一時停止することができます。
- void Sleep(
DWORD dwMilliseconds
); - 現在のスレッドをdwMillisecondsミリ秒停止する。
指定した時間(ミリ秒)経過後、その次の処理から自動的に再開されます。
パソコンのCPUは、同時に起動している複数のアプリケーションやOS本体で必要な演算を常に行っています。
昔のCPUは同時に複数の演算を行うことはできず、ひとつのアプリケーション用の演算しかできませんでした。
つまり、あるアプリケーションが長くCPUを占有すると他のアプリケーションは停止状態になってしまいます。
それでは使い勝手が悪いので、OSはそれぞれのアプリケーションからの演算要求を細かく分割し、それぞれを高速に切り替えて処理することで複数のアプリケーションを同時に実行しています。
(マルチタスク)
この処理の単位が「スレッド」です。
今のCPUは複数のスレッドを同時に処理することができますが、ひとつのアプリケーションが長くCPUを占有すべきではない点は変わりません。
Sleep関数を実行すると、呼び出し元スレッドは他の実行可能なスレッドに処理を譲ります。
呼び出し元スレッドは実行不能状態(つまり一時停止)に切り替わり、指定時間の経過後に実行可能状態に戻ります。
「時間経過後に実行される」ではなく「実行可能状態になる」のであって、すぐにスレッドが再開されるわけではなく、実際の実行のタイミングはOSが決定します。
(体感できるような遅延はありません)
Sleep関数の引数に0を指定した場合、他の実行可能なスレッドに処理を譲るのは同じですが、呼び出し元スレッドは実行可能状態のままです。
他に実行可能状態のスレッドがない場合は呼び出し元スレッドが即座に再開されます。
#include <windows.h>
#include <strsafe.h>
#include <process.h>
//ウィンドウの生成等は省略
#define BUFFERSIZE 11
typedef struct {
HWND hWnd;
unsigned int count;
} MYSTRUCT;
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
const unsigned int COUNTMAX = 100;
MYSTRUCT* ms = (MYSTRUCT*)lpParameter;
WCHAR buf[BUFFERSIZE];
unsigned int count = 0;
HDC hdc = GetDC(ms->hWnd);
while (count++ < COUNTMAX)
{
InvalidateRect((HWND)lpParameter, NULL, TRUE);
StringCchPrintf(buf, BUFFERSIZE, L"%u", ++ms->count);
TextOut(hdc, 10, 10, buf, lstrlen(buf));
Sleep(100); //100ミリ秒待機
}
ReleaseDC(ms->hWnd, hdc);
return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HANDLE hThread;
static MYSTRUCT ms;
DWORD dwExitCode;
switch (message)
{
case WM_CREATE: //ウィンドウの作成
ms.hWnd = hWnd;
break;
case WM_LBUTTONUP: //マウス左ボタンアップ
//終了コードの確認
if (GetExitCodeThread(hThread, &dwExitCode))
{
//アクティブなら何もしない
if (dwExitCode == STILL_ACTIVE)
break;
//ハンドルを閉じる
CloseHandle(hThread);
}
//新しいスレッドの作成
hThread = (HANDLE)_beginthreadex(
NULL, 0,
(LPTHREAD_START_ROUTINE)ThreadProc,
&ms, 0, NULL);
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
前回のサンプルコードにSleep関数の処理を追加しただけのコードです。
100ミリ秒(0.1秒)ごとに1ずつ数値がカウントアップしていくので目で追うことが可能になっていると思います。
停止中はCPU負荷がかからないので、ループ処理の最後に数ミリ秒SleepするだけでCPU負荷を下げることが出来ます。
メインスレッドの一時停止について
メインスレッドもSleep関数等で待機(一時停止)させることは可能です。
しかしウィンドウアプリケーションでは、メインスレッドはウィンドウの制御やユーザー入力を行っています。
メインスレッドを停止するとこれらの処理も停止するので、応答性が悪くなったりウィンドウがフリーズしたりするのでお勧めしません。
スレッドの待機
SuspendThread関数
任意のタイミングで停止/再開をコントロールしたい場合はSuspendThread
関数を使用します。
- DWORD SuspendThread(
HANDLE hThread
); - スレッドhThreadを中断する。
(中断カウントをひとつ増やす)
戻り値は関数呼び出し前の中断カウント。
失敗した場合は-1を返す。
この関数はスレッドが持つ中断カウントをひとつ増やします。
中断カウントは最初は0で、1以上のスレッドは停止状態となります。
中断カウントは最大でMAXIMUM_SUSPEND_COUNT
(127)まで増やすことが出来ます。
CreateThread
関数(_beginthreadex
関数)の第4引数にCREATE_SUSPENDED
フラグを指定すると、中断カウントが1の状態でスレッドが作成されます。
この関数を使用してスレッドを別のスレッドから停止させることはあまり推奨されません。
別スレッドからは、目的のスレッドがコード中のどのタイミングで停止するかをコントロールできないためです。
例えば共有リソースをロック中に停止させると、他のスレッドからそのリソースを使用できなくなってしまうためパフォーマンスに大幅に影響が出たりデッドロックが発生したりします。
スレッドがどのタイミングで中断されても問題が起きないことが保証できない場合は使用しないことをお勧めします。
別スレッドからのスレッドの停止については、スレッドの停止しても問題ない箇所に待機関数を記述し、セマフォやイベントなどで停止させる方法があります。
ResumeThread関数
スレッドの中断カウントはResumeThread
関数で減らすことができます。
- DWORD ResumeThread(
HANDLE hThread
); - スレッドhThreadの中断カウントをひとつ減らす。
戻り値は関数呼び出し前の中断カウント。
失敗した場合は-1を返す。
サンプルコード
#include <windows.h>
#include <strsafe.h>
#include <process.h>
//ウィンドウの生成等は省略
#define BUFFERSIZE 11
typedef struct {
HWND hwnd;
unsigned int count;
} MYSTRUCT;
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
MYSTRUCT* ms = (MYSTRUCT*)lpParameter;
WCHAR buf[BUFFERSIZE];
HDC hdc = GetDC(ms->hwnd);
while (1)
{
InvalidateRect((HWND)lpParameter, NULL, TRUE);
StringCchPrintf(buf, BUFFERSIZE, L"%u", ++ms->count);
TextOut(hdc, 10, 10, buf, lstrlen(buf));
}
ReleaseDC(ms->hwnd, hdc);
return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HANDLE hThread;
static MYSTRUCT ms;
static BOOL bSuspend;
DWORD dwExitCode;
switch (message)
{
case WM_CREATE: //ウィンドウの作成
ms.hwnd = hWnd;
hThread = (HANDLE)_beginthreadex(
NULL, 0,
(LPTHREAD_START_ROUTINE)ThreadProc,
&ms, 0, NULL);
break;
case WM_LBUTTONUP: //マウス左ボタンアップ
if (bSuspend)
ResumeThread(hThread);
else
SuspendThread(hThread);
bSuspend = !bSuspend;
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
クライアント領域をクリックする度にスレッドが停止/再開を繰り返します。