Version 14.15
スレッドを使ってみる!
「今回は、前回紹介したスレッドの使い方について説明します」
『そうそう、なんかよくわかんなかったんだよねー』
「スレッドだけじゃなく、たとえば同期オブジェクトもそうなんだけど、情
報として与えられているのは〈道具としての使い方〉だけだから。使うため
には、どういうことに使えるのか把握していないと」
『つまり、ミューテックスがバトンとして使える、っていうことを理解しな
きゃダメ、みたいな?』
「そういうこと。具体例として使い方がわからなきゃね」
『なるほど』
「というわけで実際に見ていきましょう。まず、説明を始める前に Sleep()
という API を紹介します」
『寝る、ってこと?』
「そう。この関数を呼ぶと一定時間止まります」
void UseSleep()
{
TRACE( "5 秒止まります。\n" );
Sleep( 5 * 1000 );
TRACE( "5 秒止まりました。\n" );
}
5 秒止まります。
( 5 秒経過... )
5 秒止まりました。
『ホントだ、5秒止まった!』
「 Sleep() の第1引数は待つ時間で単位はミリ秒。今回は 5000 だから 5
秒」
『で、これが?』
「この Sleep() は、この関数を呼び出したスレッドだけを止めるという機
能があります」
『呼び出したスレッドだけ……?』
「そこで、この関数を使ってスレッドを止めてみます。前回の例をちょっと
改造して」
// 別スレッドで呼び出される関数。
void __cdecl ThreadFunction( void *p_p )
{
TRACE( "スレッドです。\n" );
TRACE( "3 秒止まります。\n" );
Sleep( 3 * 1000 );
TRACE( "3 秒止まりました。\n" );
TRACE( "スレッド終了します。\n" );
}
// スレッドを作って呼び出す関数。
void UseThread()
{
_beginthread( ThreadFunction, 0, NULL );
TRACE( "スレッド呼んだよ。\n" );
TRACE( "5 秒止まります。\n" );
Sleep( 5 * 1000 );
TRACE( "5 秒止まりました。\n" );
TRACE( "関数終了します。\n" );
}
『なんか TRACE() ばっか……』
「ごちゃごちゃしてわかりにくいけど」
ThreadFunction() (スレッド側) :3秒待つ
UseThread() (呼び出す側) :5秒待つ
「ってこと」
『普通に考えると、スレッドの方が先に終わって、呼び出す側が後に終わる
よね』
「当然そう」
スレッド呼んだよ。
5 秒止まります。
スレッドです。
3 秒止まります。
3 秒止まりました。
スレッド終了します。
スレッド 0x1E0 終了、終了コード 0 (0x0)。
5 秒止まりました。
関数終了します。
「まず呼び出す側が止まり始めて、そのあとスレッドが止まり始めて、3秒
経つとスレッドが止まり終わってそのまま終了、最後に呼び出す側も5秒
経って終了、ということ」
『やっぱなんかそのままじゃん』
「んー……そっか、じゃあこれを試してみて」
// 別スレッドで呼び出される関数。
void __cdecl ThreadFunction( void *p_p )
{
TRACE( "スレッドです。\n" );
TRACE( "3 秒止まります。\n" );
Sleep( 3 * 1000 );
TRACE( "3 秒止まりました。\n" );
TRACE( "スレッド終了します。\n" );
}
// スレッドを作って呼び出す関数。
void UseThread()
{
// _beginthread( ThreadFunction, 0, NULL );
ThreadFunction( NULL );
TRACE( "スレッド呼んだよ。\n" );
TRACE( "5 秒止まります。\n" );
Sleep( 5 * 1000 );
TRACE( "5 秒止まりました。\n" );
TRACE( "関数終了します。\n" );
}
『?』
「 ThreadFunction() を、スレッドとしてじゃなく、普通に呼んでみた場合
ってこと」
『あ、なるほど』
スレッドです。
3 秒止まります。
3 秒止まりました。
スレッド終了します。
スレッド呼んだよ。
5 秒止まります。
5 秒止まりました。
関数終了します。
『 ThreadFunction() で3秒止まって、呼び出す側で5秒止まって……そっ
か、スレッドでやった時って、スレッドの方も呼び出した側も一緒に止まっ
てたけどそうじゃなかったらまず3秒止まって次に5秒止まる、ってなるん
だ』
「そういうこと。図にするとこう」
■スレッドを使わない場合
UseThread() ThreadFunction()
呼び出し→→→→→→→呼び出されました
3秒待機
(待機中……)
待機終了
戻ってきました←←←←関数終了
5秒待機
(待機中……)
待機終了
■スレッドを使う場合場合
UseThread() ThreadFunction()
呼び出し→→→→→→→別スレッドで開始
5秒待機 3秒待機
(待機中……) (待機中……)
(待機中……) (待機中……)
(待機中……) (待機中……)
(待機中……) 待機終了
(待機中……) 関数終了
(待機中……)
待機終了
『スレッドの時は並行処理……それがこういうふうに動くってことなのね』
「まぁ、難しいこと考えないで、プロセスがふたつあるようなもの、って考
える方がわかりやすいかも」
『んー……質問!』
「はい火美ちゃん」
『 ThreadFunction() って関数から抜けたらどうなるの?』
「どうもならないよ。スレッドとして呼び出した関数から抜けたら、その
スレッドはそこで終了」
『終わっちゃうんだ。あれ? UseThread() の方はどうなっちゃうんだろ
う』
「そっちは、完全に抜けるとアプリが終了します」
『アプリが?』
「 Version 8.01 ( No.143 ) で WinMain() って説明したでしょ」
『うん、一番最初に呼ばれる関数だよね』
「この関数が呼ばれるときも、スレッドが作られるんです」
『え? WinMain() が呼ばれるときも?』
「スレッドはプログラムの流れ。ってことは、プログラムを実行するときに
は必ずひとつ作られて、そのスレッドの流れに従ってプログラムが実行され
ていくわけです」
『あ! そっか、スレッドってもう必ず最初は存在してるんだ』
「そういうこと。 WinMain() から始まる処理が、最初に作られる、普段処
理しているスレッド。だから、 WinMain() から抜けるとき、このメインの
スレッドも終了して、アプリが終了するんです」
『ん? それってさっきの話と続く?』
「そうだよ。 WinMain() から関数が呼ばれまくって UseThread() が呼び出
されるんだから」
『あ……? ホントに? なんかそういう感じがしないけど』
「ボタン押すと呼ばれるしね」
『そうそう』
「というかウィンドウ使ってるとそれだけでマルチスレッドっぽいしね」
『そうだよね、それぞれのウィンドウが独立して動いてるように見えるけ
ど』
「でも実際にはスレッドはひとつ、最初に作ったものだけ。だから、ボタン
を押したときの関数も、結局は最初に作られたスレッドで呼ばれるんです」
『ウィンドウプロシージャとか通ってても?』
「そういうこと。これはあとでちゃんと説明するから。これができないと、
たとえば〈何かの処理をする間、ダイアログで進行報告&キャンセル用ダイ
アログを表示する〉っていう処理が作れません」
『あ、よくあるパターンだよね。作れない、ってことは難しいんだ』
「そういうこと。そういう、画面まわりの処理とマルチスレッドを組み合わ
せて使えるようになれば、マルチスレッドはマスターかな」
/*
Preview Next Story!
*/
『むむー、スレッドって微妙に使いづらい?』
「というかもっともっと使ってみて」
『っていっても使い方が……』
「使い方は、投げっぱなしが基本」
『投げっぱなし?』
「そ。投げて、自分は別の処理をして、終わったら待つ」
『待つ?』
「というわけで次回」
< Version 14.16 スレッドを待ってみる >
『につづく!』
「ま、使いまくればわかってくるから」
『なんかそればっか』