Version 14.16
スレッドを待ってみる
「スレッドの使い方にはいくつかパターンがあります。今回はそのひとつ、
スレッドが終わるまで待つというのをしてみます」
『スレッドが終わるまで待つ……んじゃ、スレッドを使う意味がないよう
な……』
「それはそうなんだけどね、これから複雑な処理をするための準備ってこと
で」
『う、複雑なのがあるんだ……』
「ただ、普通に待つだけなら簡単にできるよ」
// 別スレッドで呼び出される関数。
void __cdecl ThreadFunction( void *p_p )
{
TRACE( "スレッドです。\n" );
TRACE( "10 秒止まります。\n" );
Sleep( 10 * 1000 );
TRACE( "10 秒止まりました。\n" );
TRACE( "スレッド終了します。\n" );
}
// スレッドを作って呼び出して待つ関数。
void WaitThread()
{
HANDLE hThread
= (HANDLE)_beginthread( ThreadFunction, 0, NULL );
TRACE( "待機開始。\n" );
DWORD dwResult = WaitForSingleObject( hThread, 100 * 1000 );
if( dwResult == WAIT_TIMEOUT )
{
TRACE( "タイムアウト。\n" );
}
else
{
TRACE( "待機終了。\n" );
}
}
『あ! WaitForSingleObject() だ!』
「そう、同期オブジェクトを待つときに使った API です。詳しくは
Version 14.05 ( No.272 ) を参照」
『スレッドも同じように待てるの?』
「そう。まず、 _beginthread() の戻り値は、ミューテックスやセマフォと
同じ【ハンドル】なんです」
『そういえばミューテックスとかもハンドル取得してそれ使ってたね』
「このハンドルも、同期オブジェクトと同じようにシグナルが変わります」
・スレッド実行中:シグナルオフ
・スレッド終了 :シグナルオン
『なるほど、スレッドのハンドルを待てば、スレッドが終わるまで待ってい
て、終わったら待機終了、ってわけね』
「そう、こんな感じに」
待機開始。
スレッドです。
10 秒止まります。
10 秒止まりました。
スレッド終了します。
スレッド 0x798 終了、終了コード 0 (0x0)。
待機終了。
「 WaitForSingleObject() は他の同期オブジェクトと同じようにスレッド
を待ちます。そして、スレッドは終了するとシグナルがオンになります」
『なんかホントに同期オブジェクトみたい……あ、そういえば』
スレッド 0x1E0 終了、終了コード 0 (0x0)。
『って?』
「これはスレッド終了時のメッセージ。これはスレッドが終了したのを VC
が検知して勝手に出力してるものだから、気にしなくていいかな」
『むー、気にしないってゆーのもアレね……』
「さて、この方法は普通には使いません」
『そなの?』
「最初のスレッドが、立ち上げたスレッドが終わるの待ってたら、ただの
シングルスレッドでしょ」
『そか、それじゃスレッド立ち上げる意味ないね』
「だから、するとしたら間にもうひとつスレッドを挟みます」
『へ?』
// 別スレッドで呼び出される関数。
void __cdecl ThreadFunction( void *p_p )
{
TRACE( "スレッドです。\n" );
TRACE( "10 秒止まります。\n" );
Sleep( 10 * 1000 );
TRACE( "10 秒止まりました。\n" );
TRACE( "スレッド終了します。\n" );
}
// スレッドを作って呼び出して待つ関数。
// ただし、この関数も別スレッドとして呼び出されます。
void __cdecl WaitThread( void *p_p )
{
HANDLE hThread
= (HANDLE)_beginthread( ThreadFunction, 0, NULL );
TRACE( "待機開始。\n" );
DWORD dwResult = WaitForSingleObject( hThread, 100 * 1000 );
if( dwResult == WAIT_TIMEOUT )
{
TRACE( "タイムアウト。\n" );
}
else
{
TRACE( "待機終了。\n" );
}
}
// WaitThread() を別スレッドとして呼び出す関数。
// この関数をボタンのイベントハンドラとかで呼んでください。
void CallThread()
{
TRACE( "WaitThread() 呼び出し。\n" );
_beginthread( WaitThread, 0, NULL );
TRACE( "WaitThread() 呼び出し完了。\n" );
}
『関数がみっつ……』
「呼び出す順番はこう」
CallThread() :メインスレッド
↓
WaitThread() :サブスレッド1
↓
ThreadFunction() :サブスレッド2
『間の WaitThread() も別スレッドで呼ぶんだ……』
「これをダイアログアプリで試してみると、ダイアログが自由に動かせるで
しょ」
『あ、ホントだ! さっきまで固まってたのに』
「これまでの例のように、メインスレッドでスレッドを作ってそれを待って
いると、メインスレッドが止まったままになっちゃうでしょ」
『それが、もうひとつスレッド作れば、大丈夫になる……』
「もう少し実用的な例で考えてみようか」
・サブスレッド2の処理を3つ平行に行う。
・3つの処理が完了したらサブスレッド1で残処理をする。
「という例を考えてみます」
『つまりサブスレッド23つが終わったらサブスレッド1でなんかす
る……』
「こういう場合、 WaitForMultipleObjects() っていう関数を使います」
// ThreadFunction() は同じ。
// スレッドを作って呼び出して待つ関数。
// ただし、この関数も別スレッドとして呼び出されます。
void __cdecl WaitThread( void *p_p )
{
// 3つのハンドル。
HANDLE hThreadAry[3];
hThreadAry[0]
= (HANDLE)_beginthread( ThreadFunction, 0, NULL );
hThreadAry[1]
= (HANDLE)_beginthread( ThreadFunction, 0, NULL );
hThreadAry[2]
= (HANDLE)_beginthread( ThreadFunction, 0, NULL );
TRACE( "待機開始。\n" );
DWORD dwResult
= WaitForMultipleObjects( 3, hThreadAry, TRUE, 100 * 1000 );
if( dwResult == WAIT_TIMEOUT )
{
TRACE( "タイムアウト。\n" );
}
else
{
TRACE( "待機終了。\n" );
}
}
// CallThread() は同じ。
『 ThreadFunction() を3回呼んで……配列に入れてる?』
「そう、 HANDLE の配列に入れて、それを WaitForMultipleObjects() とい
う API に渡します」
『 WaitForSingleObject() の複数版、ってこと?』
「そういうこと。 WaitForMultipleObjects() の第1引数はハンドルの数、
つまり第2引数の要素数。第2引数は監視対象のハンドルの配列」
『このハンドルを待機するわけね』
「第3引数は、全部待つか、ひとつだけ待つか。 TRUE だと、すべての
ハンドルがシグナルオンになるまで待ちます」
『 FALSE だと?』
「 FALSE だとひとつだけでもシグナルオンになったら待機終了」
『へー』
「実際の流れはこう」
WaitThread() 呼び出し。
WaitThread() 呼び出し完了。
待機開始。
スレッドです。
10 秒止まります。
スレッドです。
10 秒止まります。
スレッドです。
10 秒止まります。
10 秒止まりました。
スレッド終了します。
スレッド 0x7C8 終了、終了コード 0 (0x0)。
10 秒止まりました。
スレッド終了します。
スレッド 0x3A8 終了、終了コード 0 (0x0)。
10 秒止まりました。
スレッド終了します。
スレッド 0x37C 終了、終了コード 0 (0x0)。
待機終了。
スレッド 0x658 終了、終了コード 0 (0x0)。
『ホントだ、サブスレッド2が3つとも終わってからサブスレッド2が待機
終了してる』
「こういう使い方もできるってことで」
/*
Preview Next Story!
*/
『でもなんかいまいち使い道わからない』
「というわけで次回からさらに具体的な使い方を見ていきます」
『そっちから教えてもらいたかったなー』
「スレッドだとそれは危険だから、基礎からこつこつと」
『えー?』
「というわけで次回」
< Version 14.17 スレッドで変数を共有する >
『につづく!』
「いや、本当にスレッドは危険なんだよね……」
『何泣いてんの……』