FreeBASIC マニュアルのトップに戻る

FreeBASIC ProPgMtThreads

目次→教本→プログラマーのための案内Threads←オリジナル・サイト

スレッド 左にメニュー・フレームが表示されていない場合は、ここをクリックして下さい

←リンク元に戻る プログラム開発関連に戻る

スレッド を、作成、取り外し/待機する、組み込み手続き。

序文:
プログラムの実行が開始すると、1つの暗黙的なスレッドが実行されます(これは、既に本格的なスレッドです)。
この「メイン」スレッドは、プログラムのメイン機能を実行します。
この同じプログラムは、競争力ある方法(スレッド間およびメインスレッドの両方)で実行される追加のスレッドを、明示的に起動できます。

すべてのスレッド(メインスレッドを含む)は、同じメモリを共有するため、同じグローバル変数、同じヒープメモリ、同じファイル記述子セットなどにアクセスできます。
これらのスレッドは、すべて並行して実行されます(つまり、タイム・スライスを使ったり、システムに複数のプロセッサ/コアがある場合、実際に並行して実行されます)。

スレッドを作成する
スレッドを作成するには、2つの方法があります:
- 「クラシック」メソッド Threadcreate は、別の実行スレッドで、特定のユーザー定義サブルーチン型(必ず1つのパラメーター、'Any Ptr' 型ポインターを持つ)を開始します 。この最初のメソッドは100%安全です。
- 「特定の」メソッド Threadcall は、別の実行スレッドで、ユーザー定義のサブルーチン型(ほぼ任意の数と任意の型のパラメーターを持つ可能性があります)を開始しますが、現時点では、この2番目のメソッドはバグです。

クラシックメソッド (100% safe) - Threadcreate
- 構文:
Declare Function Threadcreate ( ByVal procptr As Sub ( ByVal userdata As Any Ptr ), ByVal param As Any Ptr = 0, ByVal stack_size As Integer = 0 ) As Any Ptr
- 用法:
threadid = Threadcreate ( procptr [, [ param ] [, stack_size ] ] )
- パラメータ:
procptr
スレッドとして機能することを目的とした Sub へのポインター。 sub は、procptr と互換にするために、次の署名(同じパラメーター、同じ呼び出し規則)を持っている必要があります:
Declare Sub myThread ( ByVal userdata As Any Ptr )
userdata
スレッドとして機能することを意図した、SubAny 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
- 構文:
Declare function Threadcall subname ( [paramlist] ) as any ptr
- 用法:
threadid = Threadcall subname ( [paramlist] )
- パラメータ:
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 、別のスレッドは、このスレッドの終了を待ちます。
- そうでなければ、2番目のメソッド ThreadDetach、別のスレッドは、このスレッドを切り離して続行します。
最初の方法 - ThreadWait
- 構文:
- 用法:
ThreadWait ( threadid )
- パラメータ:
threadid
Any Ptr handle of a thread created by ThreadCreate or ThreadCall
- 注:
他の言語(C++ など)では、接尾辞 'wait()' は、'join()' と呼ばれます。

2番目の方法 - ThreadDetach
- 構文:
- 用法:
#include "fbthread.bi"
ThreadDetach ( threadid )
- パラメータ:
threadid
ThreadCreateThreadCall によって作成された、スレッドの Any Ptr ハンドル

説明
これを作成した後、プログラマーは、スレッドが、(結合)を待機するか、あるいは、これが別のスレッド(メインスレッドを含む)から切り離されることを確認する必要があります。

ThreadWait は、スレッドの実行が完了するまで待機し、スレッドハンドルに関連付けられたリソースを、解放します。
ThreadWait は、スレッド(識別子で指定)が終了するまで、戻りません。
待機中、呼び出し元は CPU 時間を消費しません。
ThreadWait は、スレッドを強制終了しません。
スレッドが、終了を強制する信号を必要とする場合、共有フラグなどの、メカニズムを使う必要があります。

ThreadDetach は、スレッド・ハンドルに関連付けられた、リソースを解放します。
スレッド・ハンドルは、ThreadDetach によって破棄され、使用できなくなります。
ThreadWait とは異なり、ThreadDetach は、スレッドの終了を待たず、その実行は独立して続行します。
スレッドが完了すると、割り当てられたすべてのリソースが、解放されます。

ThreadWaitThreadDetach が適用されると、スレッドは結合できなくなります。このため、これらのコマンドで、ハンドル識別子の値を、再使用しないでください。

通常、終了する前に、「親」スレッドは、「子」スレッドが終了するのを待っています。
しかし、プログラマーが、スレッドの終わりまで待たないことを選択した場合(そして必然的にそれだけを切り離す場合)、そのスレッドがアクセスするデータが、スレッドで終了するまで有効であることを確認する必要があります。
そうしないと、「親」スレッドが、ローカル変数への ポインター/参照 を保持し、「親」スレッドが終了しても、「子」スレッドが終了しない、という状況が発生する場合があります(変数は、スコープ外になるため、破棄されます)。

「メイン」スレッドは、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:
- 内部で結合可能なスレッド(Threadcreate ..... ThreadWait)を使って、各タイマーを並べます。
- ユーザーのイベントとして、切り離されたスレッド(Threadcreate + ThreadDetach .....)を、外部でコールバックします。
タイマー・ループから、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

参照
プログラマーのための案内に戻る
←リンク元に戻る プログラム開発関連に戻る

ページ歴史:2023-01-24 10:02:44
日本語翻訳:WATANABE Makoto、原文著作者:fxm

ホームページのトップに戻る

表示-非営利-継承