スレッド を、作成、取り外し/待機する、組み込み手続き。
序文:
プログラムの実行が開始すると、1つの暗黙的なスレッドが実行されます(これは、既に本格的なスレッドです)。
この「メイン」スレッドは、プログラムのメイン機能を実行します。
この同じプログラムは、競争力ある方法(スレッド間およびメインスレッドの両方)で実行される追加のスレッドを、明示的に起動できます。
すべてのスレッド(メインスレッドを含む)は、同じメモリを共有するため、同じグローバル変数、同じヒープメモリ、同じファイル記述子セットなどにアクセスできます。
これらのスレッドは、すべて並行して実行されます(つまり、タイム・スライスを使ったり、システムに複数のプロセッサ/コアがある場合、実際に並行して実行されます)。
スレッドを作成する
スレッドを作成するには、2つの方法があります:
- 「クラシック」メソッド
Threadcreate は、別の実行スレッドで、特定のユーザー定義サブルーチン型(必ず1つのパラメーター、
'Any Ptr' 型ポインターを持つ)を開始します 。この最初のメソッドは100%安全です。
- 「特定の」メソッド
Threadcall は、別の実行スレッドで、ユーザー定義のサブルーチン型(ほぼ任意の数と任意の型のパラメーターを持つ可能性があります)を開始しますが、現時点では、この2番目のメソッドはバグです。
クラシックメソッド (100% safe) - Threadcreate
- 構文:
- 用法:
threadid = Threadcreate ( procptr [, [ param ] [, stack_size ] ] )
- パラメータ:
procptr
スレッドとして機能することを目的とした
Sub へのポインター。 sub は、
procptr と互換にするために、次の署名(同じパラメーター、同じ呼び出し規則)を持っている必要があります:
userdata
スレッドとして機能することを意図した、
Sub の
Any Ptr パラメーター。 FreeBASIC はこのパラメータが存在することを期待しています。省略しないでください!
param
userdata パラメーターを介して
procptr が指すスレッド
Sub に渡される
Any Ptr 引数。
たとえば、これは、スレッド sub が動作するための、さまざまな情報を含む、構造体または配列へのポインタです。
param が指定されていない場合、代わりに
0 (ゼロ)がスレッドサブの
userdata パラメーターに渡されます。
stack_size
このスレッドのスタック用に予約する、オプションのバイト数。
- 戻り値:
作成されたスレッドへの
any ptr ハンドル
(threadid) 、または失敗した場合はヌルポインター (
0) 。
注意:
-
userdata パラメータは
myThread サブの本体では使わなくても構いませんが、ヘッダでは
Any Ptr パラメータとして宣言することが常に義務付けられています。
この場合,
Threadcreate を呼び出す際に対応する
param パラメータを省略するか,あるいは不要な引数を渡すことができます('0' は任意のポインタと直接互換性があるため,一般的に使われます)。
1 番目と 2 番目の例を参照してください.
- myThread にデータを渡す必要がある場合は、
Any Ptr param を使ってデータを参照できます。通常、
Threadcreate にデータを渡す前に
Any Ptr への型変換(暗黙的または明示的)が必要となり、
myThread の本体でデータを使う前に
Any Ptr からの逆型変換が必要となります。
3 番目の例をご覧ください。
特定のメソッド(バグあり)- Threadcall
- 構文:
- 用法:
- パラメータ:
subname
サブルーチンの名前
paramlist
サブルーチンに渡すパラメーターのリストで、通常のサブ呼び出しと同様。
- 戻り値:
作成されたスレッドへの
any ptr ハンドル
(threadid) 、または失敗した場合は null ポインタ (
0) 。
- 警告:
現在、
Threadcall がパラメータをスレッドに渡すことを伴う場合、スレッドが実際に起動されるまで、対応するデータが、
Threadcall 命令文の終了後も維持される保証はありません。
これは悪い動作を引き起こす可能性があります。
そのため、現時点では、代わりに
Threadcreate (100%安全)を使うことを推奨します。
説明
同じ
Sub から複数の異なるスレッドを作成でき、渡された引数によって、それぞれの動作を定義できます。
Threadcreate/
Threadcall 命令文の実行が終了してから、スレッドが効果的に起動するまでに長く時間がかかる場合があります。
このため、
Threadcreate/
Threadcall 命令文に続くいくつかの命令文は、実際のスレッドの起動前に実行できます。
逆に、スレッド本体は、
Threadcreate/
Threadcall が戻る前でも、実行を開始できます。
異なるスレッドが実行される順序について保証はないので、複数の作成スレッドが実際に実行を開始する順序を想定することはできません。(Linuxを除く)
デフォルトでは、スレッドは常に「結合可能」な状態で作成されます。
つまり、
'threadid' 識別子によって、そのハンドルにアクセスできます。
スレッドが、この状態(結合可能)で終了する場合、それに割り当てられたリソースは、自動的に解放されません(メインスレッドの終了時のみ解放されます)。
したがって、スレッドが適切に終了するためには、常に次の2つの方法のうち、1つだけを使うのが良い習慣です(以下の段落を参照):
- スレッドの終了を待機します。
- あるいは、スレッドを切り離します(スレッドはもはや結合できなくなります)。
実行中の各スレッドは、すべての実行中のスレッドの中で一意のハンドルで識別できます。
新しいスレッドが生成されると,生成関数によってそのスレッドのハンドルが返されます.
スレッドがコードを実行するとき、
ThreadSelf (fbc バージョン 1.08 以降) はスレッドのハンドルを返すことができます (暗黙のメインスレッドも独自のハンドルを持っています)。
ThreadSelf を使うと、各スレッド(暗黙のメインスレッドを含む)の固有のハンドルから、ある種の TLS(Thread Local Storage)をコード化できます。
したがって、同じグローバル変数名を定義しても、それにアクセスするスレッドに固有の格納値を持つことができます。
これにより、実行するスレッドに応じたパラメータを持つ、汎用的な手続きをコード化することができます。
スレッドの終了を待つか、スレッドを切り離す
適切なスレッド終了を誘導するには、2つの方法があります:
最初の方法 - ThreadWait
- 構文:
- 用法:
- パラメータ:
- 注:
他の言語(C++ など)では、接尾辞 'wait()' は、'join()' と呼ばれます。
2番目の方法 - ThreadDetach
説明
これを作成した後、プログラマーは、スレッドが、(結合)を待機するか、あるいは、これが別のスレッド(メインスレッドを含む)から切り離されることを確認する必要があります。
ThreadWait は、スレッドの実行が完了するまで待機し、スレッドハンドルに関連付けられたリソースを、解放します。
ThreadWait は、スレッド(識別子で指定)が終了するまで、戻りません。
待機中、呼び出し元は CPU 時間を消費しません。
ThreadWait は、スレッドを強制終了しません。
スレッドが、終了を強制する信号を必要とする場合、共有フラグなどの、メカニズムを使う必要があります。
ThreadDetach は、スレッド・ハンドルに関連付けられた、リソースを解放します。
スレッド・ハンドルは、
ThreadDetach によって破棄され、使用できなくなります。
ThreadWait とは異なり、
ThreadDetach は、スレッドの終了を待たず、その実行は独立して続行します。
スレッドが完了すると、割り当てられたすべてのリソースが、解放されます。
ThreadWait か
ThreadDetach が適用されると、スレッドは結合できなくなります。このため、これらのコマンドで、ハンドル識別子の値を、再使用しないでください。
通常、終了する前に、「親」スレッドは、「子」スレッドが終了するのを待っています。
しかし、プログラマーが、スレッドの終わりまで待たないことを選択した場合(そして必然的にそれだけを切り離す場合)、そのスレッドがアクセスするデータが、スレッドで終了するまで有効であることを確認する必要があります。
そうしないと、「親」スレッドが、ローカル変数への ポインター/参照 を保持し、「親」スレッドが終了しても、「子」スレッドが終了しない、という状況が発生する場合があります(変数は、スコープ外になるため、破棄されます)。
例
「メイン」スレッドは、10個の "M" 文字を表示し、「子」スレッドは、同時に10個の "C" 文字を表示します。
'Sleep x, 1' テンポが、各スレッド(メインスレッドと子スレッド)の
'For' ループに入れられ、タイム-スライスが解放され、他のスレッドも実行できるようになります。
テンポは、子スレッドの
'For' ループの実行時間が、メイン・スレッドの
'For' ループの実行時間よりも長くなるように設定されます。
- Threadcreate .....
ThreadWait を使う:
Declare Sub thread (ByVal userdata As Any Ptr)
Dim As Any Ptr threadID '' declaration of an 'Any Ptr' thread-ID of the child thread
Print """M"": from 'Main' thread"
Print """C"": from 'Child' thread"
Print
threadID = ThreadCreate(@thread) '' creation of the child thread from the main thread
For I As Integer = 1 To 10 '' 'For' loop of the main thread
Print "M";
Sleep 150, 1
Next I
ThreadWait(threadID) '' waiting for the child thread termination
Print
Print "'Child' thread finished"
Sleep
Sub thread (ByVal userdata As Any Ptr) '' sub executed by the child thread
For I As Integer = 1 To 10 '' 'For' loop of the child thread
Print "C";
Sleep 350, 1
Next I
End Sub
出力例:
"M": from 'Main' thread
"C": from 'Child' thread
MCMMCMMCMMCMMMCCCCCC
'Child' thread finished
- Threadcreate +
ThreadDetach ..... を使う (グローバル終了フラグが、子スレッドの最後に追加されます):
#include "fbthread.bi"
Declare Sub thread (ByVal userdata As Any Ptr)
Dim As Any Ptr threadID '' declaration of an 'Any Ptr' thread-ID of the child thread
Dim Shared As Boolean threadEnd '' declaration of a global 'Boolean' thread-End flag for the child thread
Print """M"": from 'Main' thread"
Print """C"": from 'Child' thread"
Print
threadID = ThreadCreate(@thread) '' creation of the child thread from the main thread
Threaddetach(threadID) '' detaching the child thread
For I As Integer = 1 To 10 '' 'For' loop of the main thread
Print "M";
Sleep 150, 1
Next I
While threadEnd = False '' waiting for the thread-End flag = 'True' from the child thread
Wend
Print
Print "'Child' thread finishing or finished"
Sleep
Sub thread (ByVal userdata As Any Ptr) '' sub executed by the child thread
For I As Integer = 1 To 10 '' 'For' loop of the child thread
Print "C";
Sleep 350, 1
Next I
threadEnd = True '' set the thrend-End flag to 'True'
End Sub
出力例:
"M": from 'Main' thread
"C": from 'Child' thread
MCMMCMMCMMCMMMCCCCCC
'Child' thread finishing or finished
マルチ-タイマー機能用の UDT:
タイマー・ループから、detached-thread コールバックによって、トリガーされるユーザーイベント、要求されたタイムアウトは、
Threadcreate +
ThreadDetach の実行時間(定数に関する短い時間)によってのみ偏らされ、
ThreadWait の待機によってではありません:
' Only 4 member procedures in public access (the first 3 returning 'true' if success, 'false' else):
' - Function 'Set' to parametrize the considered timer (time-out in ms, pointer to user thread)
' - Function 'Start' to start the considered timer
' - Function 'Stop' to stop the considered timer (then, the considered timer may be re-Set and re-Start)
' - Property 'Counter' to get the occurrence number of the timer
' Plus an 'Any Ptr' in public access:
' - Pointer field 'userdata' to point to any user data structure (optional usage)
'
' Remark:
' - Pointer to the considered timer instance is provided to the user thread procedure
' in order to be able to factorize the treatment per timers group,
' and to address the right user data structure if used (see example for usage).
'
' In private access:
' - 4 internal variables (time-out value, pointer to user thread, handle to timer thread, counter of occurence)
' - Static timer thread
#include "fbthread.bi"
Type UDT_timer_thread
Public:
Declare Function Set (ByVal time_out As UInteger, _
ByVal timer_procedure As Sub(ByVal param As Any Ptr)) _
As Boolean
Declare Function Start () As Boolean
Declare Function Stop () As Boolean
Declare Property Counter () As UInteger
Dim As Any Ptr userdata
Private:
Dim As UInteger tempo
Dim As Sub(ByVal param As Any Ptr) routine
Dim As Any Ptr handle
Dim As UInteger count
Declare Static Sub thread (ByVal param As Any Ptr)
End Type
Function UDT_timer_thread.Set (ByVal time_out As UInteger, _
ByVal timer_procedure As Sub(ByVal param As Any Ptr)) _
As Boolean
If timer_procedure > 0 And This.handle = 0 Then
This.tempo = time_out
This.routine = timer_procedure
This.count = 0
Function = True
Else
Function = False
End If
End Function
Function UDT_timer_thread.Start () As Boolean
If This.handle = 0 And This.routine > 0 Then
This.handle = ThreadCreate(@UDT_timer_thread.thread, @This)
Function = True
Else
Function = False
End If
End Function
Function UDT_timer_thread.Stop () As Boolean
If This.handle > 0 Then
Dim p As Any Ptr = 0
Swap p, This.handle
ThreadWait(p)
Function = True
Else
Function = False
End If
End Function
Property UDT_timer_thread.Counter () As UInteger
Return This.count
End Property
Static Sub UDT_timer_thread.thread (ByVal param As Any Ptr)
Dim As UDT_timer_thread Ptr pu = param
While pu->handle > 0
Sleep pu->tempo, 1
pu->count += 1
If pu->routine > 0 Then
Dim As Any Ptr p = ThreadCreate(Cast(Any Ptr, pu->routine), param)
Threaddetach(p)
End If
Wend
End Sub
'---------------------------------------------------------------------------------------------------
Dim As UInteger tempo1 = 950
Dim As UInteger tempo2 = 380
Dim As UDT_timer_thread timer1
timer1.userdata = New String(" callback from timer #1 (" & tempo1 & "ms)")
Dim As UDT_timer_thread timer2
timer2.userdata = New String(" callback from timer #2 (" & tempo2 & "ms)")
Sub User_thread (ByVal param As Any Ptr)
Dim As UDT_timer_thread Ptr pu = param
Dim As String Ptr ps = pu->userdata
Print *ps & ", occurrence: " & pu->Counter
End Sub
Print "Beginning of test"
If timer1.Set(tempo1, @User_thread) Then
Print " timer #1 set OK"
If timer1.Start Then
Print " timer #1 start OK"
End If
End If
If timer2.Set(tempo2, @User_thread) Then
Print " timer #2 set OK"
If timer2.Start Then
Print " timer #2 start OK"
End If
End If
Print " Then, any key to stop the timers"
Sleep
If timer1.Stop Then
Print " timer #1 stop OK"
End If
If timer2.Stop Then
Print " timer #2 stop OK"
End If
Sleep 500, 1
Print "End of test"
Delete Cast(String Ptr, timer1.userdata)
Delete Cast(String Ptr, timer2.userdata)
Sleep
出力例:
Beginning of test
timer #1 set OK
timer #1 start OK
timer #2 set OK
timer #2 start OK
Then, any key to stop the timers
callback from timer #2 (380ms), occurrence: 1
callback from timer #2 (380ms), occurrence: 2
callback from timer #1 (950ms), occurrence: 1
callback from timer #2 (380ms), occurrence: 3
callback from timer #2 (380ms), occurrence: 4
callback from timer #1 (950ms), occurrence: 2
callback from timer #2 (380ms), occurrence: 5
callback from timer #2 (380ms), occurrence: 6
callback from timer #2 (380ms), occurrence: 7
callback from timer #1 (950ms), occurrence: 3
callback from timer #2 (380ms), occurrence: 8
callback from timer #2 (380ms), occurrence: 9
callback from timer #1 (950ms), occurrence: 4
callback from timer #2 (380ms), occurrence: 10
callback from timer #2 (380ms), occurrence: 11
callback from timer #2 (380ms), occurrence: 12
timer #1 stop OK
callback from timer #1 (950ms), occurrence: 5
timer #2 stop OK
callback from timer #2 (380ms), occurrence: 13
End of test
参照