スレッドの基本

スレッドの生成はCreateThread関数で行う。
スレッドは引数を1つだけもつ関数にする。 名前は何でもいい。
スレッドは戻り値を持っていて、GetExitCodeThreadで調べることができる。
スレッドの終を待つにはWaitFor〇〇関数を使うと良いかも。

#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の使用例


#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をコメントすることで同期を使わない。

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なら非シグナル状態になる。


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

イベント