さて、通信関係のプログラムではマルチスレッドの知識が必要となってくることもあります。
今回から、マルチスレッドについて行います。
プログラムが実行されている時、少なくともひとつのスレッドが動いています。
これを複数実行しようというものです。
スレッドが複数動作していると、その実行順序は予測がつきません。
では、必要な関数を見てみましょう。
#include <windows.h> HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // セキュリティ記述子 DWORD dwStackSize, // 初期のスタックサイズ LPTHREAD_START_ROUTINE lpStartAddress, // スレッドの機能 LPVOID lpStartAddress, // スレッドの引数 DWORD dwCreationFlags, // 作成オプション LPDWORD lpThreadId // スレッド識別子 );lpThreadAttributesには、SECURITY_ATTRIBUTESへのポインタを指定します。 NULLにしてもかまいません。
dwStackSizeには、スレッド関数のスタックサイズを指定します。0にすると呼び出し側と同じになります。
lpStartAddressには、スレッド関数のアドレスを指定します。
lpStartAddressには、スレッド関数に渡す引数を指定します。
dwCreationFlagsには、スレッド関数の初期状態を指定します。
0を指定すると、初期状態からスレッドが動作します。
CREATE_SUSPENDEDを指定すると、スレッドが中断した状態で作られます。
(ResumeThread関数で動作を開始させます)
lpThreadIdには、スレッド識別子へのポインタを指定します。
関数が成功するとスレッドのハンドルが返されます。
失敗するとNULLが返されます。
スレッド関数は次のプロトタイプを持ちます。(もちろん名前は自由に付けてかまいません)
DWORD WINAPI ThreadProc( LPVOID lpParameter );この関数がreturnすると、スレッドは終了します。
また、呼び出し側では、使い終わったスレッドのハンドルはクローズしなくてはいけません。 これには、CloseHandle関数を使います。
BOOL CloseHandle( HANDLE hObject );また、VC++.netではマルチスレッドのプログラムを作る時は、前準備が必要です。 ソリューションエクスプローラでソリューションの名前を右クリックして 「プロパティ」を選択します。 「C++」の「コード生成」でランタイムライブラリを「マルチスレッド(/MT)」にします。
さて、プライマリスレッド以外のスレッドがCランタイムライブラリを使用する場合は、 CreateThread関数ではなく、_beginthreadex関数を使う必要があります。
uintptr_t _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr );引数はCreateThread関数とほぼ同じです。 では、最も簡単なマルチスレッド・プログラムを見てみましょう。
/* mult01.c */
#include <stdio.h>
#include <windows.h>
#include <process.h>
unsigned __stdcall mythread(void *);
int main()
{
DWORD thID;
HANDLE hTh;
int i;
hTh = (HANDLE)_beginthreadex(NULL, 0, mythread, NULL, 0, &thID);
for (i = 0; i < 20; i++) {
printf("Mainです[%d]\n", i);
}
if (hTh != NULL) {
CloseHandle(hTh);
printf("ハンドルクローズしました\n");
}
return 0;
}
unsigned __stdcall mythread(void *lpx)
{
int i;
for (i = 0; i < 4; i++)
printf("スレッドです[%d]\n", i);
return 0;
}
main関数側では、「Mainです[X]」を20回表示します。その後CloseHandle関数で、スレッドハンドルをクローズしています。
一方、子供スレッド側では「スレッドです[X]」を4回表示します。
では、実行結果を見てみましょう。
何回か実行してみると、スレッド側が実行されるタイミングがいろいろ変化するのがわかります。
さて、スレッド関数が終了しないうちに、本家本元のプログラムが終了するとかなりまずいです。
このサンプルプログラムでは、たいていmain関数がreturnする前に、スレッド関数が終了しますが絶対にそうなる保証はありません。
もし、スレッド側で「スレッドです[X]」と100回表示するようにするとどうでしょうか。
ほとんどの場合、スレッド関数が終了する前にmain関数が終了してしまいます。
そこで、スレッドの状態を調べてからmain関数を終了させよう、という考えに行き着きます。
スレッドの状態を調べるには、GetExitCodeThread関数を使います。
BOOL GetExitCodeThread( HANDLE hThread, LPDWORD lpExitCode // 終了ステータス );hThreadには、スレッドハンドルを指定します。
lpExitCodeには、DWORD型変数へのポインタを指定します。関数が成功するとこの変数に終了ステータスが格納されます。まだ実行中の時はSTILL_ACTIVEとなります。
では、改良版を見てみましょう。
/* mult02.c */
#include <stdio.h>
#include <windows.h>
#include <process.h>
unsigned __stdcall mythread(void *);
int main()
{
DWORD thID, dwExCode;
HANDLE hTh;
int i;
hTh = (HANDLE)_beginthreadex(NULL, 0, mythread, NULL, 0, &thID);
if (hTh == 0) {
printf("スレッド作成失敗\n");
return -1;
}
for (i = 0; i < 10; i++)
printf("Main [%d]\n", i);
while (1) {
GetExitCodeThread(hTh, &dwExCode);
if (dwExCode == STILL_ACTIVE) {
printf("スレッド稼働中\n");
} else {
break;
}
}
if (hTh != NULL) {
CloseHandle(hTh);
printf("ハンドルクローズしました\n");
}
return 0;
}
unsigned __stdcall mythread(void *lpx)
{
int i;
for (i = 0; i < 20; i++)
printf("スレッドです[%d]\n", i);
return 0;
}
main関数側では、GetExitCodeThread関数でスレッドの状態を調べてSTILL_ACTIVEであれば、無限ループをぐるぐる回ります。それ以外であれば、スレッドは終了しているので、ループを抜けて、スレッドハンドルをクローズし、プログラムを終了します。
では、実行結果を見てみましょう。
きちんとスレッドが終了するまでmain関数は終了していないことがわかりますね。
じつは、GetExitCodeThread関数をこのように使うのはかなり、例外的なことです。
本来は待機関数を使います。これについては、また後の章で解説します。
Update Apr/27/2004 By Y.Kumei