Version 14.14
スレッドを作ってみる
「今回はスレッドを作ってみます」
『スレッドって Version 14.01 ( No.268 ) で説明したのだよね、プログラ
ムの流れってゆー』
「そうです。スレッドはプログラムの流れ。プロセスひとつにつき、最初に
ひとつ作られて、プログラムを進めて行きます」
『そういう意味じゃ、プロセスの数だけスレッドがあるわけね』
「そういうこと。で、今回は、スレッドをひとつのプロセスの中でもうひと
つ作ってみます」
『おお! つまり最初の流れとは別にもうひとつ流れを作るわけね』
「そういうこと」
『……あれ? でもさ、 Version 14.04 ( No.271 ) で……』
ミューテックス ( Mutex )
セマフォ ( Semaphore )
タイマー ( Waitable timer )
イベント ( Event )
クリティカルセクション ( Critical Section )
『って、同期オブジェクトって5つ紹介してたじゃない。最後の
クリティカルセクションってまだ教えてもらってないよ?』
「実は、他の同期オブジェクトと違って、クリティカルセクションだけは複
数のプロセスで共有できないんです」
『あ、なるほど。ミューテックスとか、名前が同じひとつのミューテックス
を ProcessA や ProcessB で使えたけど、クリティカルセクションはそうい
うのできないんだ』
「だから、スレッドの説明をしてからクリティカルセクションの説明をしま
す」
『おー』
「さて、スレッドを使う前にまず断っておくことがあります」
『お、なんでしょー』
「スレッドを作る方法には、次の5種類があります」
・API の CreateThread() を使う
・ランタイムの _beginthread() を使う
・MFC の AfxBeginThread() を使う
・MFC の CWinThread を使う
・MFC のユーザーインターフェイススレッドを使う
『多!』
「で、この中で使うのは」
・ランタイムの _beginthread() を使う
「だけです」
『他は使わないの?』
「使いません。その理由は……」
・_beginthread() は一番簡単にマルチスレッドを使える。
・_beginthread() はランタイムの同期を取ってくれる。
・MFC はマルチスレッドに向いていない。
「といったところ。特に重要なのは〈ランタイムの同期を取る〉という点」
『これ以外は取らないの?』
「取らないんです。ランタイムはグローバル変数を多用してるから、普通に
複数のスレッドでランタイムを使うと問題が起きます。でも _beginthread()
を使うと自動的に同期を取ってくれるから」
『複数のスレッドがいっぺんにアクセスしない!』
「というわけ。最後の MFC だけど、 MFC はスレッドをかなり怪しい使い方
してるから、マルチスレッドと組み合わせて使わない方がいいんです」
『それって MFC のプロジェクトで使っちゃいけないってこと?』
「そうじゃなくて、新しく作ったスレッドで、 MFC を使っちゃいけない、
ってこと」
『???』
「この辺は、実際にスレッドを使ってみないとわかんないかも……」
『じゃ、使お、使お!』
「はいはい。まず、スレッドのランタイム関数を使う時には process.h と
いうヘッダーファイルをインクルードする必要があります。 StdAfx.h にこ
んな感じに追加してみて」
#include <process.h> // 追加。
//{{AFX_INSERT_LOCATION}}
「〈//{{AFX_INSERT_LOCATION}}〉って書かれているところを探して、その
上にでも追加してください」
『インクルードファイルについては Version 6.04 ( No.104 ) さんしょー』
「では、実際に使ってみます」
// 別スレッドで呼び出される関数。
void __cdecl ThreadFunction( void *p_p )
{
TRACE( "スレッドから。\n" );
}
// スレッドを作って呼び出す関数。
void UseThread()
{
_beginthread( ThreadFunction, 0, NULL );
}
「ダイアログにボタンを貼り付けて UseThread() の方を呼ぶようにしてく
ださい」
『えっと』
・ThreadFunction() : 呼ばれる方
・UseThread() : 呼ぶ方
『だよね』
「そういうこと。とりあえず使ってみて」
『ほい』
スレッドから。
『……いや、なんかすごく普通なんだけど……』
「まぁこれだけだとね。じゃあ関数の説明からしますまずは呼び出される方
から」
// 別スレッドで呼び出される関数。
void __cdecl ThreadFunction( void *p_p )
{
TRACE( "スレッドから。\n" );
}
『 __cdecl って何度か出てきてるね』
「 Version 8.01 ( No.143 ) だね。関数には〈引数と戻り値の渡し方〉に
種類があって、主に」
__cdecl
__stdcall
「のどちらかが使われます」
『普通は __cdecl 、 API とか外から呼ばれるときは __stdcall なんだよ
ね』
「そういうこと。スレッドで呼び出す関数は必ず中からだから __cdecl を
付けます」
『でもデフォルトが __cdecl なんだから付けなくてもいいんじゃない?』
「デフォルトは設定によるから、安心のために」
『ふーん』
「戻り値が void 、引数が void * なのは、 _beginthread() の引数がそう
なっているから」
『じゃあその呼び出す方』
// スレッドを作って呼び出す関数。
void UseThread()
{
_beginthread( ThreadFunction, 0, NULL );
}
『第1引数の ThreadFunction は、関数のアドレスを渡してるってヤツね』
「そう、 Version 8.07 ( No.149 ) や Version 13.17 ( No.253 ) を参
照。 _beginthread() の第1引数には以下のような関数ポインタを渡す必要
があります」
void( __cdecl *start_address )( void * )
『んと』
戻り値 (呼出方 *変数)(引数)
『なんだよね。だから戻り値が void 、引数 void * 、あと __cdecl 付け
る、ってわけね』
「そういうこと。この関数ポインタを渡すことで、 _beginthread() が中で
別スレッドとして ThreadFunction() を呼んでくれます」
『ウィンドウプロシージャとかと同じ仕組みね』
「そういうこと。じゃあ引数の説明の続き。第2引数は 0 でOK。第3引
数は、呼び出す関数の引数に渡す変数のポインタです」
『?』
「たとえば……」
void UseThread()
{
_beginthread( ThreadFunction, 0, "あいうえお" );
}
「という形で文字列のポインタを渡すと」
void __cdecl ThreadFunction( void *p_p )
{
TRACE( "%s\n", (char *)p_p );
// あいうえお
}
『あ、 p_p にさっきの〈あいうえお〉が入ってる!』
「つまり、 _beginthread() の第3引数に渡したポインタが、呼び出し先関
数の void * ポインタに入ってるってこと」
『なるほど、そうしないと引数渡せないもんね。 void * ってことは、どん
なポインタでもOK?』
「OK。 Version 7.04 ( No.124 ) で説明したように、汎用ポインタとし
て使えるから。構造体のポインタとか渡せば一度に複数の変数も渡せるし」
『なるほど。……でさ、やっぱいまいちスレッドってよくわからないんだけ
ど』
「というわけで次回に続く!」
/*
Preview Next Story!
*/
『やっぱ使ってみないとよくわかんないよね』
「特にスレッドはノウハウの塊だから」
『それをしっかり教えてもらわなきゃね』
「憶えるのも大変だけどね」
『う”』
「というわけで次回」
< Version 14.15 スレッドを使ってみる! >
『につづく!』
「こういう時は思考もマルチスレッドにしてみるとか」
『憶えるとこはひとつなんだけど』