「マルチスレッド」の編集履歴(バックアップ)一覧はこちら

マルチスレッド」(2010/08/13 (金) 17:16:06) の最新版変更点

追加された行は緑色になります。

削除された行は赤色になります。

**スレッドの基本 スレッドの生成はCreateThread関数で行う。 スレッドは引数を1つだけもつ関数にする。 名前は何でもいい。 スレッドは戻り値を持っていて、GetExitCodeThreadで調べることができる。 スレッドの終を待つにはWaitFor〇〇関数を使うと良いかも。 #asciiart(blockquote){ #N88BASIC Dim hT As HANDLE Dim id As DWord hT = CreateThread(ByVal 0, 0, AddressOf(t), 5, 0, Varptr(id)) WaitForSingleObject(hT, INFINITE) CloseHandle(hT) Print "スレッドは正しく終了しました" Function t(a As DWordPtr) As Long Print a End Function } このようにサブスレッドを作るのは簡単なんだけど、スレッド間の同期や、終了にかかあるタイミングがおかしいと、不具合のもとになる。 1CPUマシン上では、マルチスッドレは速くならない。速くなるケースは、マルチコアCPUで、かつスレッドの実際の処理がスレッドの生成や同期にかかるコストより大きくないといけない。 ウインドウプロシージャのような、場合、長大な処理をウインドウプロシージャでやると、フリーズするので、マルチスレッドにすることで、処理がすぐにカエルので、フリーズ対策になる。 つまり、Windowsのイベント処理やメッセージ処理をスレッド毎に分ける事で、1CPUでもプログラム上のメリットがあるという事。 Windows環境でスレッドを扱う場合、Win32API,C Runtime,[[MFC]]の三つの選択がある。 このうちab4では前者二つについて利用可能だろう。Win32API=CreateThread()、C Runtime=_beginthread()または_beginthreadex()である。 ※_beginthred()は[[呼出規約]]が非対応なため使えない スレッドを使う場合、スレッド内部で競合が発生すると関数を使う事が出来ない。 典型的な例はPrint文で、複数のスレッドがPrint命令を同時に実行しようとするときちんと動作しない。 これはI/Oに関する競合から起こる為だが、場合によっては計算を主とする関数でも発生する可能性はある。 正しい結果が得られないという事になる。 複数のスレッドを実行しても問題ないように設計されている関数やライブラリを、スレッドセーフである、と言う。 abに含まれる関数やライブラリはスレッドセーフを保証している訳ではないので、もしスレッド内でabの関数を 同時実行しようとすると問題が生じる可能性がある。その場合は適切に競合を回避する排他制御を 実装する(クリティカルセクション)か、スレッドセーフであるCランタイムライブラリを用いる必要があるかもしれない。 Cランタイムを利用する場合は、Win32APIのCreateThreadではなく、_beginthreadexを用いる必要がある。(AB4以前の場合) CreateThreadではCランタイムに対応していない為、例外が発生する可能性がある。 なお、開発中のAB5のThreadクラスはbeginthreadを使っている様子なので、CランタイムDLLを用いてスレッドセーフ な関数を利用可能であろう。(AB5はcdecl呼び出し規約を使える!) ***クリティカルセクション クリテカルセクションは、複数のスレッドがひとつのデータを扱うときに向いた同期の方法。 クリティカルセクションは一人しか入れない部屋のようなもので、 誰かがそこに入るとあとから来たものはそいつが出るまで待機しないといけない。 要は無理やりFIFOな環境を作るわけ。 このコードはファイルにSinとCosの値を0°から90°まで書くだけなんだけど マルチスッドレでやっているから(しかも文字列バッファも共有!)どうなるか全く予想できない。 5行目の#define DOUKIをコメントすることで同期を使わない。 #asciiart(blockquote){ Declare Function WaitForMultipleObjects Lib"kernel32"(c As Word, pobj As *HANDLE, waitall As Long, ms As DWord) As DWord Declare Function sprintf CDECL Lib"msvcrt"(buf As *Byte, fmt As *Byte, ...) As DWord #N88BASIC #define DOUKI Dim hT[ELM(2)] As HANDLE Dim id As DWord Dim hF As HANDLE Dim mes[666] As Byte Dim w As DWord Dim cs As CRITICAL_SECTION InitializeCriticalSection(cs) hF = CreateFile("sincos.txt", GENERIC_WRITE, 0, ByVal 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0) hT[0] = CreateThread(ByVal 0, 0, AddressOf(sint), 0, 0, Varptr(id)) hT[1] = CreateThread(ByVal 0, 0, AddressOf(cost), 0, 0, Varptr(id)) WaitForMultipleObjects(2, hT, TRUE, INFINITE) CloseHandle(hT[0]) CloseHandle(hT[1]) CloseHandle(hF) DeleteCriticalSection(cs) Print "スレッドは正しく終了しました" 'スレッド関数その1 Function sint(a As DWordPtr) As Long Dim n As DWord #ifdef DOUKI EnterCriticalSection(cs) #endif For n=0 To 90 sprintf(mes, Ex"sin%2d = %6.4f\r\n", n, Sin(ToRad(n))) WriteFile(hF, mes, lstrlen(mes), VarPtr(w), ByVal 0) Next #ifdef DOUKI LeaveCriticalSection(cs) #endif ExitThread(TRUE) End Function 'スレッド関数その2 Function cost(a As DWordPtr) As Long Dim n As DWord #ifdef DOUKI EnterCriticalSection(cs) #endif For n=0 To 90 sprintf(mes, Ex"cos%2d = %6.4f\r\n", n, Cos(ToRad(n))) WriteFile(hF, mes, lstrlen(mes), VarPtr(w), ByVal 0) Next #ifdef DOUKI LeaveCriticalSection(cs) #endif ExitThread(TRUE) End Function 'ラジアン変換 Function ToRad(d As Double) As Double ToRad = d * 3.1416 /180.000 End Function } ***セマフォ セマフォは同時に動くスレッドの上限を決めることができる。 わかりやすく言えばダウンローダーの同時接続数の制限ができるってこと。 最初にCreateSemaphore関数で初期カウントと最大カウントを決定。 ReleaseSemaphore関数でカウンタを増やす。 WaitForなんとか関数でカウンタが減る。 カウンタが0以上ならシグナル状態で0なら非シグナル状態になる。 #asciiart(blockquote){ Declare Function WaitForMultipleObjects Lib"kernel32"(c As Word, pobj As *HANDLE, waitall As Long, ms As DWord) As DWord Declare Function CreateSemaphore Lib"kernel32" Alias "CreateSemaphoreA" (sa As VoidPtr, iv As Long, mv As Long, na As *Byte) As HANDLE Declare Function ReleaseSemaphore Lib"kernel32" (hs As HANDLE, dec As Long, p As *DWord) As HANDLE #N88BASIC Const MAXTHREAD = ELM(10) Dim hT[MAXTHREAD] As HANDLE Dim hS As HANDLE Dim id As DWord Dim i As Long Dim count As QWord Dim cs As CRITICAL_SECTION InitializeCriticalSection(cs) hS = CreateSemaphore(0, 3,3,"平井公彦") For i=0 To MAXTHREAD hT[i] = CreateThread(ByVal 0, 0, AddressOf(test), i, 0, Varptr(id)) Next WaitForMultipleObjects(MAXTHREAD+1, hT, TRUE, INFINITE) For i=0 To MAXTHREAD CloseHandle(hT[i]) Next DeleteCriticalSection(cs) Print "スレッドは正しく終了しました" 'スレッド関数 Function test(id As *DWord) As DWord Dim ipv6 As DWord WaitForSingleObject(hS ,INFINITE) EnterCriticalSection(cs) ipv6=id count++ Print "スレッド[";ipv6;"]開始",count LeaveCriticalSection(cs) Sleep(2580) EnterCriticalSection(cs) count-- Print "スレッド[";ipv6;"]終了",count LeaveCriticalSection(cs) ReleaseSemaphore(hS,1, NULL) End Function } ***イベント
**スレッドの基本 スレッドの生成はCreateThread関数で行う。 スレッドは引数を1つだけもつ関数にする。 名前は何でもいい。 スレッドは戻り値を持っていて、GetExitCodeThreadで調べることができる。 スレッドの終を待つにはWaitFor〇〇関数を使うと良いかも。 #asciiart(blockquote){ #N88BASIC Dim hT As HANDLE Dim id As DWord hT = CreateThread(ByVal 0, 0, AddressOf(t), 5, 0, Varptr(id)) WaitForSingleObject(hT, INFINITE) CloseHandle(hT) Print "スレッドは正しく終了しました" Function t(a As DWordPtr) As Long Print a End Function } このようにサブスレッドを作るのは簡単なんだけど、スレッド間の同期や、終了にかかあるタイミングがおかしいと、不具合のもとになる。 1CPUマシン上では、マルチスッドレは速くならない。速くなるケースは、マルチコアCPUで、かつスレッドの実際の処理がスレッドの生成や同期にかかるコストより大きくないといけない。 ウインドウプロシージャのような、場合、長大な処理をウインドウプロシージャでやると、フリーズするので、マルチスレッドにすることで、処理がすぐにカエルので、フリーズ対策になる。 つまり、Windowsのイベント処理やメッセージ処理をスレッド毎に分ける事で、1CPUでもプログラム上のメリットがあるという事。 Windows環境でスレッドを扱う場合、Win32API,C Runtime,[[MFC]]の三つの選択がある。 このうちab4では前者二つについて利用可能だろう。Win32API=CreateThread()、C Runtime=_beginthread()または_beginthreadex()である。 ※_beginthred()は[[呼出規約]]が非対応なため使えない スレッドを使う場合、スレッド内部で競合が発生すると関数を使う事が出来ない。 典型的な例はPrint文で、複数のスレッドがPrint命令を同時に実行しようとするときちんと動作しない。 これはI/Oに関する競合から起こる為だが、場合によっては計算を主とする関数でも発生する可能性はある。 正しい結果が得られないという事になる。 複数のスレッドを実行しても問題ないように設計されている関数やライブラリを、スレッドセーフである、と言う。 abに含まれる関数やライブラリはスレッドセーフを保証している訳ではないので、もしスレッド内でabの関数を 同時実行しようとすると問題が生じる可能性がある。その場合は適切に競合を回避する排他制御を 実装する(クリティカルセクション)か、スレッドセーフであるCランタイムライブラリを用いる必要があるかもしれない。 Cランタイムを利用する場合は、Win32APIのCreateThreadではなく、_beginthreadexを用いる必要がある。(AB4以前の場合) CreateThreadではCランタイムに対応していない為、例外が発生する可能性がある。 なお、開発中のAB5のThreadクラスはbeginthreadを使っている様子なので、CランタイムDLLを用いてスレッドセーフ な関数を利用可能であろう。(AB5はcdecl呼び出し規約を使える!) ***_beginthreadexの使用例 #asciiart(blockquote){ #console 'スレッドのてすと Declare Function _beginthreadex CDECL Lib "msvcrt" _ (ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, _ dwStackSize As DWord, _ lpStartAddress As DWord, _ lpParameter As VoidPtr, _ dwCreationFlags As DWord, _ lpThreadId As DWordPtr) As HANDLE Declare Sub _endthreadex CDECL Lib "msvcrt" (exitcode As DWord) Declare Function getchar CDECL Lib "msvcrt" () As Long Function sureddo(arg As *DWord) Print "sureddo" _endthreadex(55555) End Function Dim handoru As DWord, co As DWord handoru = _beginthreadex(ByVal 0, 0, AddressOf(sureddo), 0, 0, 0)'CreateThreadと引数は同じ。 WaitForSingleObject(handoru, 3333) GetExitCodeThread(handoru, co) CloseHandle(handoru) Print "スレッドの終了コードは";co;"であります" Print "キーイン待ち" getchar() } ***クリティカルセクション クリテカルセクションは、複数のスレッドがひとつのデータを扱うときに向いた同期の方法。 クリティカルセクションは一人しか入れない部屋のようなもので、 誰かがそこに入るとあとから来たものはそいつが出るまで待機しないといけない。 要は無理やりFIFOな環境を作るわけ。 このコードはファイルにSinとCosの値を0°から90°まで書くだけなんだけど マルチスッドレでやっているから(しかも文字列バッファも共有!)どうなるか全く予想できない。 5行目の#define DOUKIをコメントすることで同期を使わない。 #asciiart(blockquote){ Declare Function WaitForMultipleObjects Lib"kernel32"(c As Word, pobj As *HANDLE, waitall As Long, ms As DWord) As DWord Declare Function sprintf CDECL Lib"msvcrt"(buf As *Byte, fmt As *Byte, ...) As DWord #N88BASIC #define DOUKI Dim hT[ELM(2)] As HANDLE Dim id As DWord Dim hF As HANDLE Dim mes[666] As Byte Dim w As DWord Dim cs As CRITICAL_SECTION InitializeCriticalSection(cs) hF = CreateFile("sincos.txt", GENERIC_WRITE, 0, ByVal 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0) hT[0] = CreateThread(ByVal 0, 0, AddressOf(sint), 0, 0, Varptr(id)) hT[1] = CreateThread(ByVal 0, 0, AddressOf(cost), 0, 0, Varptr(id)) WaitForMultipleObjects(2, hT, TRUE, INFINITE) CloseHandle(hT[0]) CloseHandle(hT[1]) CloseHandle(hF) DeleteCriticalSection(cs) Print "スレッドは正しく終了しました" 'スレッド関数その1 Function sint(a As DWordPtr) As Long Dim n As DWord #ifdef DOUKI EnterCriticalSection(cs) #endif For n=0 To 90 sprintf(mes, Ex"sin%2d = %6.4f\r\n", n, Sin(ToRad(n))) WriteFile(hF, mes, lstrlen(mes), VarPtr(w), ByVal 0) Next #ifdef DOUKI LeaveCriticalSection(cs) #endif ExitThread(TRUE) End Function 'スレッド関数その2 Function cost(a As DWordPtr) As Long Dim n As DWord #ifdef DOUKI EnterCriticalSection(cs) #endif For n=0 To 90 sprintf(mes, Ex"cos%2d = %6.4f\r\n", n, Cos(ToRad(n))) WriteFile(hF, mes, lstrlen(mes), VarPtr(w), ByVal 0) Next #ifdef DOUKI LeaveCriticalSection(cs) #endif ExitThread(TRUE) End Function 'ラジアン変換 Function ToRad(d As Double) As Double ToRad = d * 3.1416 /180.000 End Function } ***セマフォ セマフォは同時に動くスレッドの上限を決めることができる。 わかりやすく言えばダウンローダーの同時接続数の制限ができるってこと。 最初にCreateSemaphore関数で初期カウントと最大カウントを決定。 ReleaseSemaphore関数でカウンタを増やす。 WaitForなんとか関数でカウンタが減る。 カウンタが0以上ならシグナル状態で0なら非シグナル状態になる。 #asciiart(blockquote){ Declare Function WaitForMultipleObjects Lib"kernel32"(c As Word, pobj As *HANDLE, waitall As Long, ms As DWord) As DWord Declare Function CreateSemaphore Lib"kernel32" Alias "CreateSemaphoreA" (sa As VoidPtr, iv As Long, mv As Long, na As *Byte) As HANDLE Declare Function ReleaseSemaphore Lib"kernel32" (hs As HANDLE, dec As Long, p As *DWord) As HANDLE #N88BASIC Const MAXTHREAD = ELM(10) Dim hT[MAXTHREAD] As HANDLE Dim hS As HANDLE Dim id As DWord Dim i As Long Dim count As QWord Dim cs As CRITICAL_SECTION InitializeCriticalSection(cs) hS = CreateSemaphore(0, 3,3,"平井公彦") For i=0 To MAXTHREAD hT[i] = CreateThread(ByVal 0, 0, AddressOf(test), i, 0, Varptr(id)) Next WaitForMultipleObjects(MAXTHREAD+1, hT, TRUE, INFINITE) For i=0 To MAXTHREAD CloseHandle(hT[i]) Next DeleteCriticalSection(cs) Print "スレッドは正しく終了しました" 'スレッド関数 Function test(id As *DWord) As DWord Dim ipv6 As DWord WaitForSingleObject(hS ,INFINITE) EnterCriticalSection(cs) ipv6=id count++ Print "スレッド[";ipv6;"]開始",count LeaveCriticalSection(cs) Sleep(2580) EnterCriticalSection(cs) count-- Print "スレッド[";ipv6;"]終了",count LeaveCriticalSection(cs) ReleaseSemaphore(hS,1, NULL) End Function } ***イベント

表示オプション

横に並べて表示:
変化行の前後のみ表示: