マルチスレッドで、"
危険領域(Critical Sections)" に関連する質問。
1. ミューテックスによって複数のスレッド間で1つのシェア変数を保護することが必須ではないのはどのような場合ですか?
複数のスレッド間で、共有変数にアクセスする場合、すべてのアクセスは、通常、すべてのスレッドで、ブロック
Mutexlock...Mutexunlock 内に配置する必要があります:
- 共有変数が、 size <= sizeof(integer) (アクセス用のアセンブラー命令1つのみ)の、唯一の単純な定義済み数値型の場合、ミューテックスの使用は必須ではありません。
- しかし、これがたとえば、 win32 コンパイルで、共有変数 LongInt の場合、ここでは mutex を使うことを推奨します(そうしないと、スレッドによる読み取りフェーズが、別のスレッドの書き込みフェーズと、交ざる場合があります)。
これは、メモリ内の変数にアクセス(読み取りまたは書き込み)するために、プロセッサが、内部レジスタを使うためです。
N-ビット・プロセッサには、N-ビット・レジスタがありますが、それ以上のものはありません:
- したがって、1つのアセンブラ命令のみで、メモリ内の N ビット変数にアクセスできます。
- 逆に、2N-ビット変数にアクセスするには、2つのアセンブラー命令を使う必要があります。
- これら2つのアセンブラー命令(書き込み用)の間に、別のスレッドがこの同じ変数にアクセス(読み取り用)すると、取得した値は、つじつまが合わなく(互いに、最高 N-ビットと最低 N ビットの不整合)なる可能性があります。
この動作は、2つのスレッドと、mutex なしの共有
LongInt (64-bit) を使う描画プログラムで確認できます:
- 32 ビットでコンパイルすると、多くの読み取り値で、一貫性が失われます。
- 64 ビットでコンパイルすると、一貫性がない読み取り値は、有りません。
下のテスト・プログラムをコンパイルします:
- 32-bit => の場合、多くの誤った点が、円上ではなく、円を含む正方形のどこかにあります。
ミューテックスを起動するため、37/39/58/60 の 4行のコメントを解除すると、取得したすべての点は、円上にのみ、表示されます。
- 64-bit => の場合、ミューテックスが起動していない場合でも、すべての点は、円上だけで有効です。
' - The "user-defined thread" computes the points coordinates on a circle,
' and write those in a LongInt (32-bit & 32-bit = 64-bit)
' - The "main thread" plots the points from the LongInt value.
'
' Behavior:
' - The first point must be pre-determined.
' - Nothing prevents that a same calculated point could be plotted several times
' (depends on execution times of the loops between main thread and user thread).
' - Nothing prevents that a calculated point could be not plotted
' (same remark on the loop times).
'
' Remark:
' Voluntarily, there is no Sleep in the loop of each thread (normally strongly discouraged),
' but this is just in this special case to amplify the behavior effects to observe.
Union Point2D
Dim As LongInt xy
Type
Dim As Long y
Dim As Long x
End Type
End Union
Dim As Any Ptr handle
Dim Shared As Any Ptr mutex
Dim Shared As Integer quit
Sub Thread (ByVal param As Any Ptr)
Const pi As Single = 4 * Atn(1)
Dim As Point2D Ptr p = param
Do
Dim As Point2D P2D0
Dim As Single teta = 2 * pi * Rnd
P2D0.x = 320 + 200 * Cos(teta)
P2D0.y = 240 + 200 * Sin(teta)
' Mutexlock(mutex)
p->xy = P2D0.xy
' Mutexunlock(mutex)
' Sleep 5, 1
Loop Until quit = 1
End Sub
Screen 12
Dim As Point2D P2D
P2D.x = 520
P2D.y = 240
mutex = MutexCreate
handle = ThreadCreate(@Thread, @P2D)
Dim As Integer c
Do
Dim As Point2D P2D0
' Mutexlock(mutex)
P2D0.xy = P2D.xy
' Mutexunlock(mutex)
PSet (P2D0.x, P2D0.y), c
c = (c Mod 15) + 1
' Sleep 5, 1
Loop Until Inkey <> ""
quit = 1
ThreadWait(handle)
MutexDestroy(mutex)
2. 2つのスレッド間で競合する、2つの危険領域(critical section)(ミューテックス・ロックと条件変数シグナリングで)の、コード実行の順序はどうなりますか?
発生する、1つのスレッド・シグナリングの順序:
a) 別のスレッドが待機している間(述語の While ループ内)、
b) 別のスレッドが待機する前(述語の While ループ内)。
#define while_loop_on_predicate
Dim As Any Ptr handle
Dim Shared As Any Ptr mutex
Dim Shared As Any Ptr cond
Dim As Integer sleep0
Dim As Integer sleep1
#ifdef while_loop_on_predicate
Dim Shared As Integer ready
#endif
Sub Thread1 (ByVal param As Any Ptr)
Sleep *Cast(Integer Ptr, param), 1
MutexLock(mutex)
Color 11 : Print " Thread#1 locks the mutex"
Color 11 : Print " Thread#1 executes code with exclusion"
#ifdef while_loop_on_predicate
ready = 1
#endif
Color 11 : Print " Thread#1 is signaling"
CondSignal(cond)
Color 11 : Print " Thread#1 executes post-code with exclusion"
Color 11 : Print " Thread#1 unlocks the mutex"
MutexUnlock(mutex)
End Sub
Sub Thread0 (ByVal param As Any Ptr)
Sleep *Cast(Integer Ptr, param), 1
MutexLock(mutex)
Color 10 : Print " Thread#0 locks the mutex"
Color 10 : Print " Thread#0 executes pre-code with exclusion"
#ifdef while_loop_on_predicate
While ready <> 1
#endif
Color 10 : Print " Thread#0 is waiting"
CondWait(cond, mutex)
Color 10 : Print " Thread#0 is waked"
#ifdef while_loop_on_predicate
Wend
#endif
Color 10 : Print " Thread#0 executes code with exclusion"
#ifdef while_loop_on_predicate
ready = 0
#endif
Color 10 : Print " Thread#0 unlocks the mutex"
MutexUnlock(mutex)
End Sub
mutex = MutexCreate
cond = CondCreate
sleep0 = 0
sleep1 = 1000
Color 7 : Print "Chronology for Thread#1 signaling while Thread#0 is waiting:"
handle = ThreadCreate(@Thread1, @sleep1)
Thread0(@sleep0)
ThreadWait(handle)
Color 7 : Print "Thread#1 finished": Print
Sleep 1000, 1
sleep0 = 1000
sleep1 = 0
Color 7 : Print "Chronology for Thread#1 signaling before Thread#0 is waiting:"
handle = ThreadCreate(@Thread1, @sleep1)
Thread0(@sleep0)
ThreadWait(handle)
Color 7 : Print "Thread#1 finished": Print
MutexDestroy(mutex)
CondDestroy(cond)
Sleep
出力部分 a - スレッド #0 が待機中に、スレッド #1 がシグナルを送る順序:
Chronology for Thread#1 signaling while Thread#0 is waiting:
Thread#0 locks the mutex
Thread#0 executes pre-code with exclusion
Thread#0 is waiting
Thread#1 locks the mutex
Thread#1 executes code with exclusion
Thread#1 is signaling
Thread#1 executes post-code with exclusion
Thread#1 unlocks the mutex
Thread#0 is waked
Thread#0 executes code with exclusion
Thread#0 unlocks the mutex
Thread#1 finished
出力部分 b - スレッド #0 が待機する前の、スレッド #1 がシグナルする順序:
Chronology for Thread#1 signaling before Thread#0 is waiting:
Thread#1 locks the mutex
Thread#1 executes code with exclusion
Thread#1 is signaling
Thread#1 executes post-code with exclusion
Thread#1 unlocks the mutex
Thread#0 locks the mutex
Thread#0 executes pre-code with exclusion
Thread#0 executes code with exclusion
Thread#0 unlocks the mutex
Thread#1 finished
注意:CondWait が述語の
While ループ内にない場合(上のプログラムの最初の行にコメントを入れる)、2番目のケース(スレッド #1 は、スレッド #0 が待機する前に信号を送る)で、スレッド #0 は待機フェーズ(Ctrl-C で終了)でブロックされたままであることを、確認できます。
3. ミューテックスをロックしないで 'Condsignal()' や 'Condbroadcast()' を呼び出すと、どうなりますか?
危険領域(critical section) の例 2 を参照して、次のことを思い出してください:
- スレッドを起動するために、Condsignal() か Condbroadcast() を実行している間は、ミューテックスも、常にロックする必要があります(ミューテックスがロック解除されるのは、Condsignal() か Condbroadcast() の後だけです)。
- ミューテックスがロックされていない場合(または、Condsignal() か Condbroadcast() を実行する直前のみで、ミューテックスがロック解除されている場合でも)、動作は、予測できない可能性があります(スレッドの構成と実行の実時間に応じて、動作したりしなかったり) 。
危険領域(Critical Section) の例2 "
すべてのスレッドに対して、condwait を使ってから condbroadcast(および mutex)を使う、同期メソッドの例":
- 少なくとも Mutexunlock() が、Condbroadcast() の直前に移動されると、プログラムは直ちにハングします。
- 一部の人は、ミューテックスは、常に Condsignal() か Condbroadcast() の直前に、ロック解除できると認定しますが、Condbroadcast() に対してのみロック解除ができる、と、より慎重に主張するユーザーもいます。しかし、実験では逆のことが示されています!
一般的なルールは、次のとおりです:
- 条件は、スレッドがミューテックスをロックしてから、条件変数 (CondWait()) で待機するまでの間、(Condsignal() か Condbroadcast() によって) 通知されてはなりません。
そうしないと、その条件変数のスレッドの待機キューが、破損する可能性があるようです。
- したがって、それを回避して、このルールに従うには、条件が通知されたときに、ミューテックスがロックされたままであることが必要です。
4. ブール述語('Condsignal' か 'Condbroadcast' を動作させる前に、他のスレッドによって設定される)をチェックするために、'While...Wend' ループ内に 'Condwait' を入れることはなぜ必須ですか?
While predicate <> True
Condwait(conditionalid, mutexid)
Wend
predicate = False
すべてのドキュメントで、こうすることを強く推奨します。主に、最終的な偽のウェイクアップと戦うために、正当化されます。
これはおそらく真実ですが、受信スレッドがまだ
CondWait を待機していない間に、信号が早期にアクティブになった場合(信号は永久に失われます)、
CondSignal (または
CondBroadcast)が失われないようにすることを推奨します:
- この場合、受信スレッドは、CondSignal (又は CondBroadcast)が起動される前に、まだミューテックスをロックしていません。
- したがって、受信スレッドが While ループに到達する前に、述語はすでに true になり、CondWait が完全にスキップされるようになり、最終的なブロック現象を回避します。
2つのスレッド(メイン・プログラムのスレッド #0、ユーザー手続きのスレッド #1、それぞれが、ループでその番号を出力します)が、ほぼ同じ実行時間を持ち、それぞれが、その番号を織り合わせるために互いに同期します( 1つの mutex と、2つの条件変数と、
CondSignal/
CondWait を使って):
- 述語の While ループがない場合、プログラムは直ぐにハングします(Ctrl-C で終了):
' Thread#0 XOR + <==> Thread#1
' ..... .....
' MutexLock(mut) <-------------------------. .----------> MutexLock(mut)
' ( atomic_mutex_unlock(mut) ) ------. | | Do_something_with_exclusion
' CondWait(cond#1, mut) <----------- | --- | ----------- | ---------- CondSignal(cond#1)
' ( atomic_mutex_re-lock(mut) ) <--- | ----'----. | .----- ( atomic_mutex_unlock(mut) )
' Do_something_with_exclusion | .--- | ------ | --- | ---> CondWait(cond#2, mut)
' CondSignal(cond#2) --------------- | ----' | .---'---- | ---> ( atomic_mutex_re-lock(mut) )
' Do_something_with_exclusion | .--- | ---' | Do_something_with_exclusion
' MutexUnlock(mut) ------------------'-----' '--------------'----- MutexUnlock(mut)
' ..... .....
'
' (connecting lines join the sender(s) and receiver(s) impacted by each action occurring during the sequence)
Dim As Any Ptr handle
Dim Shared As Any Ptr mutex
Dim Shared As Any Ptr cond1
Dim Shared As Any Ptr cond2
Dim Shared As Integer quit
Sub Thread (ByVal param As Any Ptr)
Do
MutexLock(mutex)
Print "1";
CondSignal(cond1)
CondWait(cond2, mutex)
If quit = 1 Then
MutexUnlock(mutex)
Exit Do
End If
MutexUnlock(mutex)
Sleep 1, 1
Loop
End Sub
mutex = MutexCreate
cond1 = CondCreate
cond2 = CondCreate
handle = ThreadCreate(@Thread)
Do
MutexLock(mutex)
CondWait(cond1, mutex)
Print "0";
CondSignal(cond2)
If Inkey <> "" Then
quit = 1
MutexUnlock(mutex)
Exit Do
End If
MutexUnlock(mutex)
Sleep 1, 1
Loop
ThreadWait(handle)
MutexDestroy(mutex)
CondDestroy(cond1)
CondDestroy(cond2)
Print
Sleep
- 各 CondWait の周りの述語に 'While...Wend' ループを使うと、ブロッキング現象は発生しません:
' Thread#0 XOR + <==> Thread#1
' ..... .....
' MutexLock(mut) <-----------------------------. .----> MutexLock(mut)
' While bool#1 <> true <---------------------- | --------. | Do_something_with_exclusion
' ( atomic_mutex_unlock(mut) ) ------. | '--------------- | ---- bool#1 = true
' CondWait(cond#1, mut) <----------- | --- | ------------------------ | ---- CondSignal(cond#1)
' ( atomic_mutex_re-lock(mut) ) <--- | ----'----. .--------------- | ---> While bool#2 <> true
' Wend | | | .---- | -------- ( atomic_mutex_unlock(mut) )
' bool#1 = false .------- | -------- | ---' .------ | --- |--------> CondWait(cond#2, mut)
' Do_something_with_exclusion | .--- | -------- | ------' .--- | ----'--------> ( atomic_mutex_re-lock(mut) )
' bool#2 = true ----------------' | | .--- | ---------' | Wend
' CondSignal(cond#2) ---------------' | | | | bool#2 = false
' Do_something_with_exclusion | | | | Do_something_with_exclusion
' MutexUnlock(mut) ----------------------'-----' '---------------'----------- MutexUnlock(mut)
' ..... .....
'
' (connecting lines join the sender(s) and receiver(s) impacted by each action occurring during the sequence)
Dim As Any Ptr handle
Dim Shared As Any Ptr mutex
Dim Shared As Any Ptr cond1
Dim Shared As Any Ptr cond2
Dim Shared As Integer new1
Dim Shared As Integer new2
Dim Shared As Integer quit
Sub Thread (ByVal param As Any Ptr)
Do
MutexLock(mutex)
Print "1";
new1 = 1
CondSignal(cond1)
While new2 <> 1
CondWait(cond2, mutex)
Wend
new2 = 0
If quit = 1 Then
MutexUnlock(mutex)
Exit Do
End If
MutexUnlock(mutex)
Sleep 1, 1
Loop
End Sub
mutex = MutexCreate
cond1 = CondCreate
cond2 = CondCreate
handle = ThreadCreate(@Thread)
Do
MutexLock(mutex)
While new1 <> 1
CondWait(cond1, mutex)
Wend
new1 = 0
Print "0";
new2 = 1
CondSignal(cond2)
If Inkey <> "" Then
quit = 1
MutexUnlock(mutex)
Exit Do
End If
MutexUnlock(mutex)
Sleep 1, 1
Loop
ThreadWait(handle)
MutexDestroy(mutex)
CondDestroy(cond1)
CondDestroy(cond2)
Print
Sleep
4.1. 'Condwait' (および 'Condsignal' や 'Condbroadcast')は、既に、他のスレッドによって設定されたブール述語をチェックする 'While...Wend' ループが存在しても、まだ有益ですか?
(前の質問によって引き起こされた別の質問)
- 推奨される構造は次のとおりです:
' Principle of mutual exclusion + CONDWAIT in a While...Wend loop with predicate check, for a thread sub-section
' (connecting lines join the sender(s) and receiver(s) impacted by each action occurring during the sequence)
'
'
' Thread Other Thread
' MUTEXLOCK(mutexID) <------------------------- from ( atomic_mutex_unlock(mutexID) ) or MUTEXUNLOCK(mutexID)
' .......
' While booleanT <> True <--------------------- from booleanT = True
' ( atomic_mutex_unlock(mutexID) ) -------> to MUTEXLOCK(mutexID) or ( atomic_mutex_re-lock(mutexID) )
' CONDWAIT(conditionalID, mutexID) <------- from CONDSIGNAL(conditionalID)
' ( atomic_mutex_re-lock(mutexID) ) <------ from ( atomic_mutex_unlock(mutexID) ) or MUTEXUNLOCK(mutexID)
' Wend
' booleanT = False
' .......
' MUTEXUNLOCK(mutexID) -----------------------> to MUTEXLOCK(mutexID) or ( atomic_mutex_re-lock(mutexID) )
' Principle of mutual exclusion + CONDSIGNAL with predicate check, for a thread sub-section
' (connecting lines join the sender(s) and receiver(s) impacted by each action occurring during the sequence)
'
' Thread Other Thread
' MUTEXLOCK(mutexID) <------------------------- from ( atomic_mutex_unlock(mutexID) ) or MUTEXUNLOCK(mutexID)
' .......
' booleanOT = True ---------------------------> to While booleanOT <> True
' CONDSIGNAL(conditionalID) ------------------> to CONDWAIT(conditionalID, mutexID)
' .......
' MUTEXUNLOCK(mutexID) -----------------------> to MUTEXLOCK(mutexID) or ( atomic_mutex_re-lock(mutexID) )
- 'CondWait' を使わない場合、代わりに、ブール・フラグの 'While...Wend' ループに 'Sleep x, 1' 命令を配置して、ループ時にタイム・スライスを解放する必要があります。
(さらに、 この 'Sleep x, 1' は、別のスレッドを解放するために ['Mutexunlock'...'Mutexlock'] ブロック内に配置する必要があります):
' Principle of mutual exclusion + SLEEP in a While...Wend loop with predicate check, for a thread sub-section
' (connecting lines join the sender(s) and receiver(s) impacted by each action occurring during the sequence)
'
' Thread Other Thread
' MUTEXLOCK(mutexID) <------------------------- from MUTEXUNLOCK(mutexID)
' .......
' While booleanT <> True <--------------------- from booleanT = True
' MUTEXUNLOCK(mutexID) -------------------> to MUTEXLOCK(mutexID)
' SLEEP(tempo, 1)
' MUTEXLOCK(mutexID) <--------------------- from MUTEXUNLOCK(mutexID)
' Wend
' booleanT = False
' .......
' MUTEXUNLOCK(mutexID) -----------------------> to MUTEXLOCK(mutexID)
' Principle of mutual exclusion + predicate check only, for a thread sub-section
' (connecting lines join the sender(s) and receiver(s) impacted by each action occurring during the sequence)
'
' Thread Other Thread
' MUTEXLOCK(mutexID) <------------------------- from MUTEXUNLOCK(mutexID)
' .......
' booleanOT = True ---------------------------> to While booleanOT <> True
' .......
' MUTEXUNLOCK(mutexID) -----------------------> to MUTEXLOCK(mutexID)
'CondWait' の間、スレッドの実行は中断され、条件変数が通知されるまで、CPU 時間を消費しません。
ただし、代わりに 'Sleep x, 1' が設定されている場合、待機時間は事前に決定されていて、'CondWait' のような自己適応ではありません。
=> 'CondWait' は、実行時間を最適化するのに役立ちます。
5. ユーザー行入力関数を完全にスレッド-セーフで実装する方法は?
別のスレッドも入出力リソースにアクセスする必要がある場合、
Input キーワードは、スレッド-セーフではない場合があります:
- Input 命令文を実行するとき、他の実行中のスレッドは、テキスト・カーソルの位置を変更してはなりません。これにより、Locate、Print、...などの命令文が禁止されます。
- さらに、Input キーワードを、ミューテックス・ロックで囲むことはできません(Inkey キーワードでできるように)。入力行は完了せず検証されますが、入力/出力にアクセスしたい他のスレッドは完全にブロックされるためです。 (ミューテックスのロック解除を待っています)。
スレッド-セーフな入力行関数(入力/出力リソースに対して):
入力位置、プロンプト・メッセージ、スリープ時間、ライン-ブランキング・コマンド、mutex ポインターは、
Inkey キーワードの周りをまわることで、単純化された入力関数をシミュレートする、スレッドセーフな、次の
threadInput() 関数に渡すことができます。(すべての入出力キーワードは、相互排他ロック・ブロックで囲まれている必要があり、カーソル位置は、相互排他ロック・ブロックの終了ごとに、復元する必要があります):
Function threadInput (ByVal row As Integer, ByVal column As Integer, ByRef prompt As String = "", _
ByVal sleeptime As Integer = 15, ByVal blank As Integer = 0, ByVal mutex As Any Ptr = 0 _
) As String
Dim As String inputchr
Dim As String inputline
Dim As Integer cursor
Dim As Integer cursor0
Dim As Integer r
Dim As Integer c
MutexLock(mutex)
r = CsrLin()
c = Pos()
Locate row, column
Print prompt & " _";
cursor0 = Pos() - 1
Locate r, c
MutexUnlock(mutex)
Do
MutexLock(mutex)
r = CsrLin()
c = Pos()
inputchr = Inkey
If inputchr <> "" Then
If inputchr >= Chr(32) And inputchr < Chr(255) Then
inputline = Left(inputline, cursor) & inputchr & Mid(inputline, cursor + 1)
cursor += 1
ElseIf inputchr = Chr(08) And Cursor > 0 Then 'BkSp
cursor -= 1
inputline = Left(inputline, cursor) & Mid(inputline, cursor + 2)
ElseIf inputchr = Chr(255) & "S" And Cursor < Len(inputline) Then 'Del
inputline = Left(inputline, cursor) & Mid(inputline, cursor + 2)
ElseIf inputchr = Chr(255) + "M" And Cursor < Len(inputline) Then 'Right
Cursor += 1
ElseIf inputchr = Chr(255) + "K" And Cursor > 0 Then 'Left
Cursor -= 1
End If
If inputchr = Chr(27) Then 'Esc
Locate row, cursor0
Print Space(Len(inputline) + 1);
inputline = ""
cursor = 0
End If
Locate row, cursor0
Print Left(inputline, cursor) & Chr(95) & Mid(inputline, cursor + 1) & " ";
End If
Locate r, c
MutexUnlock(mutex)
Sleep sleeptime, 1
Loop Until inputchr = Chr(13)
If blank <> 0 Then
MutexLock(mutex)
r = CsrLin()
c = Pos()
Locate row, cursor0
Print Space(Len(inputline) + 1);
Locate r, c
MutexUnlock(mutex)
End If
Return inputline
End Function
- Critical Sections ページの「すべてのスレッドにミューテックスを使用した非同期メソッドの例」の、例1から、実行中のマルチ・スレッド・コードは、プログラムを終了するために "quit" コマンドを待っています:
' User thread algorithm:
'
' Do
' | Mutexlock
' | .....
' | Critical section of code
' | .....
' | Mutexunlock
' | Sleep my_tempo, 1
' Loop Until quit = true
'
' There is no any advantage or disadvantage between threads for running their critical sections.
Function threadInput (ByVal row As Integer, ByVal column As Integer, ByRef prompt As String = "", _
ByVal sleeptime As Integer = 15, ByVal blank As Integer = 0, ByVal mutex As Any Ptr = 0 _
) As String
Dim As String inputchr
Dim As String inputline
Dim As Integer cursor
Dim As Integer cursor0
Dim As Integer r
Dim As Integer c
MutexLock(mutex)
r = CsrLin()
c = Pos()
Locate row, column
Print prompt & " _";
cursor0 = Pos() - 1
Locate r, c
MutexUnlock(mutex)
Do
MutexLock(mutex)
r = CsrLin()
c = Pos()
inputchr = Inkey
If inputchr <> "" Then
If inputchr >= Chr(32) And inputchr < Chr(255) Then
inputline = Left(inputline, cursor) & inputchr & Mid(inputline, cursor + 1)
cursor += 1
ElseIf inputchr = Chr(08) And Cursor > 0 Then 'BkSp
cursor -= 1
inputline = Left(inputline, cursor) & Mid(inputline, cursor + 2)
ElseIf inputchr = Chr(255) & "S" And Cursor < Len(inputline) Then 'Del
inputline = Left(inputline, cursor) & Mid(inputline, cursor + 2)
ElseIf inputchr = Chr(255) + "M" And Cursor < Len(inputline) Then 'Right
Cursor += 1
ElseIf inputchr = Chr(255) + "K" And Cursor > 0 Then 'Left
Cursor -= 1
End If
If inputchr = Chr(27) Then 'Esc
Locate row, cursor0
Print Space(Len(inputline) + 1);
inputline = ""
cursor = 0
End If
Locate row, cursor0
Print Left(inputline, cursor) & Chr(95) & Mid(inputline, cursor + 1) & " ";
End If
Locate r, c
MutexUnlock(mutex)
Sleep sleeptime, 1
Loop Until inputchr = Chr(13)
If blank <> 0 Then
MutexLock(mutex)
r = CsrLin()
c = Pos()
Locate row, cursor0
Print Space(Len(inputline) + 1);
Locate r, c
MutexUnlock(mutex)
End If
Return inputline
End Function
'------------------------------------------------------------------------------
Type UDT
Dim As Integer number
Dim As Integer tempo
Dim As Any Ptr pThread
Dim As ULongInt count
Static As Any Ptr pMutex
Static As Integer numberMax
Static As Integer quit
End Type
Dim As Any Ptr UDT.pMutex
Dim As Integer UDT.numberMax
Dim As Integer UDT.quit
Sub Counter (ByVal pt As UDT Ptr)
With *pt
Locate .number, .number, 0
Sleep 5, 1
.count += 1
Print .count;
End With
End Sub
Sub Thread (ByVal p As Any Ptr)
Dim As Integer myquit
Dim As UDT Ptr pUDT = p
With *pUDT
Do
MutexLock(.pMutex)
Counter(pUDT)
myquit = .quit
MutexUnlock(.pMutex)
Sleep .tempo, 1
Loop Until myquit = 1
End With
End Sub
Screen 12
UDT.numberMax = 6
Dim As UDT u(0 To UDT.numberMax)
For I As Integer = 0 To UDT.numberMax
u(I).number = i
u(I).tempo = 100 + 15 * I - 95 * Sgn(I)
Next I
UDT.pMutex = MutexCreate
Dim As Single t = Timer
For I As Integer = 1 To UDT.numberMax
u(I).pThread = ThreadCreate(@Thread, @u(I))
Next I
Do
Loop Until LCase(threadInput(8, 1, """quit"" for exit?", 10, 1, UDT.pMutex)) = "quit"
UDT.quit = 1
For I As Integer = 1 To UDT.numberMax
ThreadWait(u(I).pThread)
Next I
t = Timer - t
MutexDestroy(UDT.pMutex)
Dim As ULongInt c
For I As Integer = 1 To UDT.numberMax
c += u(I).count
Next I
Locate UDT.numberMax + 4, 1
Print CULngInt(c / t) & " increments per second"
Sleep
注意:
それ以外では、スレッドで、
Line,
Draw String,
Put として、描画キーワードだけを使って(描画カーソルの位置だけを使う)、mutex なしでメインコードの
Line Input キーワードと互換性のあるスレッド-セーフ手続きを誘導します:
Type UDT
Dim As Integer number
Dim As Integer tempo
Dim As Any Ptr pThread
Dim As ULongInt count
Dim As Any Ptr img
Static As Integer numberMax
Static As Integer quit
End Type
Dim As Integer UDT.numberMax
Dim As Integer UDT.quit
Const As String prompt = "Enter ""quit"" for exit"
Dim As String s
Sub Counter (ByVal pt As UDT Ptr) ' for a graphic character size 8x16
With *pt
Line .img, (0, 0)-(20 * 8 - 1, 16 - 1), 0, BF ' clearing the image buffer
Sleep 5, 1
.count += 1
Draw String .img, (0, 0), Str(.count) ' drawing in the image buffer
Put ((.number - 1) * 8, (.number - 1) * 16), .img, PSet ' copying the image buffer to screen
End With
End Sub
Sub Thread (ByVal p As Any Ptr) ' for a graphic character size 8x16
Dim As UDT Ptr pUDT = p
With *pUDT
.img = ImageCreate(20 * 8, 16) ' using an image buffer to avoid flickering
Do
Counter(pUDT)
Sleep .tempo, 1
Loop Until .quit = 1
ImageDestroy .img ' destroying the image buffer
End With
End Sub
Screen 12
UDT.numberMax = 6
Dim As UDT u(0 To UDT.numberMax)
For I As Integer = 0 To UDT.numberMax
u(I).number = i
u(I).tempo = 100 + 15 * I - 95 * Sgn(I)
Next I
Dim As Single t = Timer
For I As Integer = 1 To UDT.numberMax
u(I).pThread = ThreadCreate(@Thread, @u(I))
Next I
Do
Locate 8, 1, 0
Line Input; prompt; s
Locate , Len(prompt) + 3
Print Space(Len(s));
Loop Until LCase(s) = "quit"
UDT.quit = 1
For I As Integer = 1 To UDT.numberMax
ThreadWait(u(I).pThread)
Next I
t = Timer - t
Dim As ULongInt c
For I As Integer = 1 To UDT.numberMax
c += u(I).count
Next I
Locate UDT.numberMax + 4, 1
Print CULngInt(c / t) & " increments per second"
Sleep
6. マルチ-スレッドで 'Screenlock' を使う方法は?
- Screenlock...Scrennunlock ブロックは、マルチスレッドと互換性がありません(そうでなければ、プログラムがハングします)。
これが、ミューテックス・ブロックが、相互排除を確実にするために、このような各ブロックの周りに、使われる必要がある理由です。
- 入力キーワード(キーボード、マウスなど)は、画面がロックされていると、安全に実行できないため、このようなキーワードは
Screenlock...Screenunlock ブロックの外側にある必要があり、そして、独自のスレッドの
Screenlock...Screenunlock ブロックの外側にある必要があります。また、mutex ブロックによって、他のスレッドの、すべての
Screenlock...Screenunlock ブロックを保護します。
したがって、キー入力や行入力を待機する
Getkey と
Input 命令文は使えませんが、待機しない
Inkey は機能します。
いくつかのルールを綿密に適用することにより、スレッド内で
Screenlock/
Screenunlock を使用できます。
メインコード(メインスレッド)を含む、すべてのスレッドの、コーディングの原則:
Do
' instructions without display (printing/drawing, ...) neither input (input/inkey/mouse getting, ...)
MutexLock(m)
Screenlock
' instructions with only display (printing/drawing, ...)
Screenunlock
' instructions with only input without waiting (inkey/mouse getting, ...)
MutexUnlock(m)
Sleep tempo, 1
Loop Until condition
- たとえば、1つの Mutexlock...Mutexunlock ブロックを、各 Screenlock...Screenunlock ブロックの周りに使い、Inkey 命令自体が常に Screenlock...Screenunlock ブロックの外側にある必要がある Inkey 命令の周りに、もう1つ使う必要があります:
Type ThreadUDT
Dim handle As Any Ptr
Static sync As Any Ptr
Static quit As Byte
End Type
Dim ThreadUDT.sync As Any Ptr
Dim ThreadUDT.quit As Byte
Function ClockTime () As String
Return Time
End Function
Function Counter () As Integer
Static C As Integer
C = (C + 1) Mod 1000000
Return C
End Function
Sub ProcedureThread (ByVal param As Any Ptr)
With *Cast(ThreadUDT Ptr, param)
Do
MutexLock(.sync)
ScreenLock
Line (544, 0)-(639, 49), 0, BF 'clear the print area
Sleep 100, 1
Locate 2, 71
Print ClockTime();
ScreenUnlock
MutexUnlock(.sync)
Sleep 100, 1
Loop Until .quit = 1
End With
End Sub
Screen 12
Locate 30, 2
Print "<q/Q> : quit";
Dim TTptr As ThreadUDT Ptr = New ThreadUDT
ThreadUDT.sync = MutexCreate
TTptr->handle = ThreadCreate(@ProcedureThread, TTptr)
Dim As String s
Do
MutexLock(ThreadUDT.sync)
ScreenLock
Line (296, 208)-(376, 256), 0, BF 'clear the print area
Sleep 100, 1
Locate 15,40
Print Using "######"; Counter();
ScreenUnlock
s = Inkey
MutexUnlock(ThreadUDT.sync)
Sleep 100, 1
Loop Until LCase(s) = "q"
ThreadUDT.quit = 1
ThreadWait(TTptr->handle)
MutexDestroy(ThreadUDT.sync)
Delete TTptr
注意: 'clear the print area' 行の直後にある
Sleep x, 1 キーワードは、画面ロックを使わない場合に、点滅を強調するためにだけ、ここにあります。
7. マルチ-スレッドで、「ビデオ・ページング(ダブル・バッファリングかページ・フリッピング)」を使う方法は?
「画面ロック」(上の段落を参照)の代わりに、「ビデオ・ページング(ダブル・バッファリングまたはページ・フリッピング)」をマルチスレッドでより簡単に使用できます。ただ、gfxlib2 の多くの状態が、Screenset ( また、 View 設定、描画カーソル位置、描画カラーなど)のように、スレッドに依存しているこに注意して下さい。
このため、マルチ・ビデオページ構成で動作する各スレッド・コードで、作業ページと表示ページの設定を常に制御する必要があります。
- ダブル・バッファリング・メソッドの例(各ステップで、各スレッドは、除外ミューテックス・コード・ブロック内から、作業ページを更新し、それを表示ページにコピーする必要があります):
Type ThreadUDT
Dim handle As Any Ptr
Static sync As Any Ptr
Static quit As Byte
End Type
Dim ThreadUDT.sync As Any Ptr
Dim ThreadUDT.quit As Byte
Function ClockTime () As String
Return Time
End Function
Function Counter () As Integer
Static C As Integer
C = (C + 1) Mod 1000000
Return C
End Function
Sub ProcedureThread (ByVal param As Any Ptr)
ScreenSet 1, 0 '' setting to define in each thread
With *Cast(ThreadUDT Ptr, param)
Do
MutexLock(.sync)
Line (544, 0)-(639, 49), 0, BF '' clear the print area
Sleep 100, 1
Locate 2, 71
Print ClockTime();
ScreenCopy
MutexUnlock(.sync)
Sleep 100, 1
Loop Until .quit = 1
End With
End Sub
Screen 12, , 2
ScreenSet 1, 0 '' setting to define in each thread
Locate 30, 2
Print "<q/Q> : quit";
ScreenCopy
Dim TTptr As ThreadUDT Ptr = New ThreadUDT
ThreadUDT.sync = MutexCreate
TTptr->handle = ThreadCreate(@ProcedureThread, TTptr)
Dim s As String
Do
MutexLock(ThreadUDT.sync)
Line (296, 208)-(376, 256), 0, BF '' clear the print area
Sleep 100, 1
Locate 15,40
Print Using "######"; Counter();
ScreenCopy
s = Inkey
MutexUnlock(ThreadUDT.sync)
Sleep 100, 1
Loop Until LCase(s) = "q"
ThreadUDT.quit = 1
ThreadWait(TTptr->handle)
MutexDestroy(ThreadUDT.sync)
Delete TTptr
注意: 'clear the print area' 行の直後にある
Sleep x, 1 キーワードは、ダブルバッファリングが使われていない場合のちらつきを強調するためのものです。
- 2ページのフリップ方法の例(各ステップで、各スレッドは、同じ除外ミューテックス・コード・ブロック内から、2つの画面ページを、更新しフリップする必要があります):
Type ThreadUDT
Dim handle As Any Ptr
Static sync As Any Ptr
Static quit As Byte
End Type
Dim ThreadUDT.sync As Any Ptr
Dim ThreadUDT.quit As Byte
Function ClockTime () As String
Return Time
End Function
Function Counter () As Integer
Static C As Integer
C = (C + 1) Mod 1000000
Return C
End Function
Sub ProcedureThread (ByVal param As Any Ptr)
Dim p0 As Integer = 0
Dim p1 As Integer = 1
ScreenSet 1, 0 '' setting to define in each thread
With *Cast(ThreadUDT Ptr, param)
Do
MutexLock(.sync)
Dim s As String = ClockTime()
For I As Integer = 1 To 2 '' updating the two screen pages
Line (544, 0)-(639, 49), 0, BF '' clear the print area
Sleep 100, 1
Locate 2, 71
Print s;
ScreenSet p0, p1
Swap p0, p1
Next I
MutexUnlock(.sync)
Sleep 100, 1
Loop Until .quit = 1
End With
End Sub
Screen 12, , 2
Dim p0 As Integer = 0
Dim p1 As Integer = 1
ScreenSet 1, 0 '' setting to define in each thread
For I As Integer = 1 To 2 '' updating the two screen pages
Locate 30, 2
Print "<q/Q> : quit";
ScreenSet p0, p1
Swap p0, p1
Next I
Dim TTptr As ThreadUDT Ptr = New ThreadUDT
ThreadUDT.sync = MutexCreate
TTptr->handle = ThreadCreate(@ProcedureThread, TTptr)
Dim s As String
Do
MutexLock(ThreadUDT.sync)
Dim C As Integer = Counter()
For I As Integer = 1 To 2 '' updating the two screen pages
Line (296, 208)-(376, 256), 0, BF '' clear the print area
Sleep 100, 1
Locate 15,40
Print Using "######"; c;
ScreenSet p0, p1
Swap p0, p1
Next I
s = Inkey
MutexUnlock(ThreadUDT.sync)
Sleep 100, 1
Loop Until LCase(s) = "q"
ThreadUDT.quit = 1
ThreadWait(TTptr->handle)
MutexDestroy(ThreadUDT.sync)
Delete TTptr
注意: 'clear the print area' 行の直後の
Sleep x, 1 キーワードは、2ページめくりが使われていない場合に、ちらつきを強調するためにだけここにあります。
注意: これらの 2つの例では、相互排他ミューテックス・コード・ブロックが、2つのスレッドで必須です。コンソール命令文 + Inkey を使うためだけでなく、ダブル・バッファリング・メソッドを使う、描画命令文 + Screencopy だけを使うためです。
(ちらつき防止プロセスがない場合、描画命令文は、排他ミューテックス・コード・ブロックの外側にあることができます。)
8. マルチ-スレッドで、マルチ-スレッド・アプリケーション(gfxlib2)に、 FB ランタイム・ライブラリを使うには?
gfxlib2 のソースコードは、TLS(Thread Local Storage)を使って多くの状態を保存するため、多くのことはスレッド固有です。
gfxlib2 はスレッドセーフであるため、描画命令文自体(Draw String を含む)で、スレッド間のミューテックスを除外する必要はありません。
対照的に、Locate, Print, ...などのコンソール命令文は、前述のようにスレッドセーフではありません(たとえば、テキスト・カーソルの位置は、すべてのスレッドに共通です)。
- 描画の状態(描画カーソルの位置、描画の色など)がスレッドに依存することを示す、簡単な例:
Screen 12
Sub thread(ByVal p As Any Ptr)
Color 10
PSet(150, 10)
For I As Integer = 1 To 40
Line -Step(10, 10)
Sleep 150, 1
Next I
Draw String Step (-40, 10), "user thread"
End Sub
Dim As Any Ptr p = ThreadCreate(@thread)
Color 14
PSet(10, 100)
For I As Integer = 1 To 24
Line -Step(10, 10)
Sleep 250, 1
Next I
Draw String Step (-40, 10), "main thread"
ThreadWait(p)
Color 15
Locate 4, 2
Print "Any key for exit"
Sleep
- スレッド内の、描画命令文(Line、Draw String、Screencopy など)が、排他(ミューテックスによる)を使わずに、別のスレッド内のコンソール命令文(Inkey など)と競合できることを示す例:
#include "vbcompat.bi"
Screen 12, , 2
ScreenSet 1, 0
Color 0, 7
Cls
Dim Shared terminate As Integer = 0
Sub thread (ByVal param As Any Ptr)
ScreenSet 1, 0
Do
Line (16, 432)-Step(96, 32), 11, BF 'clear print area
Sleep 100, 1
Draw String (24, 432), Format(Now,"dd/mm/yyyy"), 0
Draw String (32, 448), Format(Now,"hh:mm:ss"), 0
ScreenCopy
Sleep 100, 1
Loop Until terminate = 1
End Sub
Dim As String reply
Locate 2, 2
Print "Enter ""q"" to quit"
ScreenCopy
Dim p As Any Ptr = ThreadCreate(@thread)
Do
reply = Inkey
Sleep 100, 1
Loop Until LCase(reply) = "q"
Print " Stop the thread"
ScreenCopy
terminate=1
ThreadWait (p)
Print " Thread terminated"
ScreenCopy
Sleep
注意: 'clear the print area' 行の直後の
Sleep x, 1 キーワードは、ダブル・バッファリングが使われていない場合に、ちらつきを強調するためにのみ、ここにあります。
- 上記の例から、表示する日付と、表示する時間が、2つの独立したスレッドである場合、これら2つのスレッド間の相互排他ミューテックス・コード・ブロックは必須です。
描画命令文自体が競合するためではなく、これら2つのスレッドを競合させる、ダブル・バッファリング・メソッド(ちらつきに対して)のみが原因です:
#include "vbcompat.bi"
Screen 12, , 2
ScreenSet 1, 0
Color 0, 7
Cls
Dim Shared terminate As Integer = 0
Dim Shared mutex As Any Ptr
Sub thread1 (ByVal param As Any Ptr)
ScreenSet 1, 0
Do
MutexLock(mutex)
Line (16, 432)-Step(96, 16), 11, BF 'clear the print area
Sleep 200, 1
Draw String (24, 432), Format(Now,"dd/mm/yyyy"), 0
ScreenCopy
MutexUnlock(mutex)
Sleep 100, 1
Loop Until terminate = 1
End Sub
Sub thread2 (ByVal param As Any Ptr)
ScreenSet 1, 0
Do
MutexLock(mutex)
Line (16, 448)-Step(96, 16), 11, BF 'clear the print area
Sleep 100, 1
Draw String (32, 448), Format(Now,"hh:mm:ss"), 0
ScreenCopy
MutexUnlock(mutex)
Sleep 100, 1
Loop Until terminate = 1
End Sub
Dim As String reply
Locate 2, 2
Print "Enter ""q"" to quit"
ScreenCopy
mutex = MutexCreate
Dim p1 As Any Ptr = ThreadCreate(@thread1)
Dim p2 As Any Ptr = ThreadCreate(@thread2)
Do
reply = Inkey
Sleep 100, 1
Loop Until LCase(reply) = "q"
Print " Stop the threads"
ScreenCopy
terminate=1
ThreadWait (p1)
ThreadWait (p2)
MutexDestroy(mutex)
Print " Threads terminated"
ScreenCopy
Sleep
注意: 'clear the print area' 行の直後の
Sleep x, 1 キーワードは、ダブル・バッファリングが使われていない場合、またはミューテックスが使われていない場合に、ちらつきを強調するためだけにあります。
9. コンソール命令文とキーボード入力を、マルチ-スレッドで使うには?
コンソール命令文(
Locate,
Print,
Color, ...など)、および Graphics ウィンドウの
Locate と
Print(Graphics ウィンドウの
Color は除く)、およびキーボード入力(
Inkey,
Getkey,
Input, ...など)は、スレッド-セーフではありません:
- したがって、異なるスレッドの競合セクションで使用される場合、相互排他は、ブロックの最後(自身の使用の後)で、以前(ブロックの始まり)と同様に、コードが状態(テキストカーソルの位置、コンソールの色など)を復元できる、ミューテックス・ロック・ブロックによって必須です。
- ただし、Getkey か Input キーワードは、ミューテックス・ロック・ブロック内に含めることはできません(Inkey キーワードは実行できます)。なぜなら、キーボード入力が完了しない限り、他の競合するスレッドは、完全にブロックされる(ミューテックスのロック解除を待っています)からです。
- Locate と Print キーワードが、コンソール・ウィンドウまたは描画ウィンドウに適用された場合に、スレッドセーフではないことを示す例(テキスト・カーソル は、2つのケースでスレッドに依存していないことを示します):
Sub Thread (ByVal p As Any Ptr)
Locate Cast(Integer, p), Cast(Integer, p)
For I As Integer = 1 To 50 - 2 * Cast(Integer, p)
Sleep 20 * Cast(Integer, p), 1
Print Str(Cast(Integer, p));
Next I
End Sub
Sub test ()
Dim As Any Ptr p(1 To 9)
For I As Integer = 1 To 9
p(I) = ThreadCreate(@Thread, Cast(Any Ptr, I))
Sleep 25, 1
Next I
For I As Integer = 1 To 9
ThreadWait(p(I))
Next I
End Sub
Screen 0
test()
Locate 15, 1
Print "Any key to continue"
Sleep
Screen 12
test()
Locate 15, 1
Print "Any key to quit"
Sleep
注意: 各スレッドは、コンソール・ウィンドウと描画ウィンドウの、スレッド番号(1〜9 の ID)に対応する独自の行に、書き込みしないことがわかります。
- 上の例から、スレッド・コードは、ミューテックス・ロック・ブロックおよび、カーソルの移動の前/後にカーソルの状態を保存/復元することにより、競合するセクションで、完了しています:
Dim Shared As Any Ptr mutex
Sub Thread (ByVal p As Any Ptr)
MutexLock(mutex)
Dim As Long l0 = Locate()
Locate Cast(Integer, p), Cast(Integer, p)
Dim As Long l = Locate()
Locate HiByte(LoWord(l0)), LoByte(LoWord(l0)), HiWord(l0)
MutexUnlock(mutex)
For I As Integer = 1 To 50 - 2 * Cast(Integer, p)
Sleep 20 * Cast(Integer, p), 1
MutexLock(mutex)
l0 = Locate()
Locate HiByte(LoWord(l)), LoByte(LoWord(l)), HiWord(l)
Print Str(Cast(Integer, p));
l = Locate()
Locate HiByte(LoWord(l0)), LoByte(LoWord(l0)), HiWord(l0)
MutexUnlock(mutex)
Next I
End Sub
Sub test ()
Dim As Any Ptr p(1 To 9)
For I As Integer = 1 To 9
p(I) = ThreadCreate(@Thread, Cast(Any Ptr, I))
Sleep 25, 1
Next I
For I As Integer = 1 To 9
ThreadWait(p(I))
Next I
End Sub
mutex = MutexCreate
Screen 0
test()
Locate 15, 1
Print "Any key to continue"
Sleep
Screen 12
test()
Locate 15, 1
Print "Any key to quit"
Sleep
MutexDestroy(mutex)
注意: 各スレッドが、コンソール・ウィンドウと、描画ウィンドウの、スレッド番号(1〜9の ID)に対応する自身の行に書き込みを行っていることがわかります。
- Color キーワードが、コンソール・ウィンドウに適用されるとスレッドセーフではないが、描画ウィンドウに適用されるとスレッドセーフであることを示す例 (この場合、色はスレッドに依存しています):
Sub Thread (ByVal p As Any Ptr)
Color Cast(Integer, p) + 8, Cast(Integer, p)
For I As Integer = 1 To 50 - 2 * Cast(Integer, p)
Print " " & Cast(Integer, p) & " ";
Sleep 20 * Cast(Integer, p), 1
Next I
End Sub
Sub test ()
Dim As Any Ptr p(1 To 9)
Locate 1, 1
For I As Integer = 1 To 9
p(I) = ThreadCreate(@Thread, Cast(Any Ptr, I))
Sleep 25, 1
Next I
For I As Integer = 1 To 9
ThreadWait(p(I))
Next I
Locate 16, 1
End Sub
Screen 0
test()
Print "Any key to continue"
Sleep
Screen 12
test()
Print "Any key to quit"
Sleep
注意: 前景/背景色は、コンソール・ウィンドウの、スレッド番号(1〜9 の ID)に固有ではないことがわかります。しかし、これは描画ウィンドウではうまく機能します。
- 上の例から、スレッド・コードは、ブロックをミューテックス・ロックし、独自のカラー値の使用の前/後に、カラー状態を保存/復元することにより、競合するセクションで完了しています:
Dim Shared As Any Ptr mutex
Sub Thread (ByVal p As Any Ptr)
MutexLock(mutex)
Dim As Ulong c0 = Color(Cast(Integer, p) + 8, Cast(Integer, p))
Dim As Ulong c = Color()
Color(LoWord(c0), HiWord(c0))
MutexUnlock(mutex)
For I As Integer = 1 To 50 - 2 * Cast(Integer, p)
MutexLock(mutex)
c0 = Color(LoWord(c), HiWord(c))
Print " " & Cast(Integer, p) & " ";
Color(LoWord(c0), HiWord(c0))
MutexUnlock(mutex)
Sleep 20 * Cast(Integer, p), 1
Next I
End Sub
Sub test ()
Dim As Any Ptr p(1 To 9)
Locate 1, 1
For I As Integer = 1 To 9
p(I) = ThreadCreate(@Thread, Cast(Any Ptr, I))
Sleep 25, 1
Next I
For I As Integer = 1 To 9
ThreadWait(p(I))
Next I
Locate 16, 1
End Sub
mutex = MutexCreate
Screen 0
test()
Print "Any key to continue"
Sleep
Screen 12
test()
Print "Any key to quit"
Sleep
MutexDestroy(mutex)
注意: 前景/背景色は、コンソール・ウィンドウの、スレッド番号(1〜9 の ID)に、固有であることがわかります(明らかに、これは描画ウィンドウでは常に機能します)。
したがって、スレッドの競合セクションで、Getkey や Input を使うと:
- 単一スレッド(メイン・スレッドなど)だけが、競合するセクションで、コンソール命令文(Locate, Print, Color, ...など)と Inkey に加えて、Getkey や Input を使えます。
- 他のスレッドは、競合するセクションで、コンソール命令文やキーボード入力キーワードを使ってはなりません。しかし、コンソール描画命令文(Pset, Line, Circle, Draw String, graphic Color, ...など)は、それ自体がスレッドセーフなので使えます。(問題なく、メインスレッドでグラフィカルに走査できます。)
- Input と Getkey は、スレッドの競合セクションでの画面ロックの使用も、除外します(ちらつき防止方法として、ダブル・バッファリングを推奨します)。
- スレッド(ここではユーザースレッド)の描画命令文(Line や Draw String や Screencopy など)は、除外を使わずに、別のスレッド(ここではメインスレッド)の、コンソール命令文(Locate や Print や Input など)と、競合できることを示す例 (ミューテックスによる):
#include "vbcompat.bi"
Screen 12, , 2
ScreenSet 1, 0
Color 0, 7
Cls
Dim Shared terminate As Integer = 0
Sub thread (ByVal param As Any Ptr)
ScreenSet 1, 0
Do
Line (16, 432)-Step(96, 32), 11, BF 'clear the print area
Sleep 100, 1
Draw String (24, 432), Format(Now,"dd/mm/yyyy"), 0
Draw String (32, 448), Format(Now,"hh:mm:ss"), 0
ScreenCopy
Sleep 100, 1
Loop Until terminate = 1
End Sub
Dim As String reply
Locate 2, 2
Print "Enter ""quit"" to quit"
ScreenCopy
Dim p As Any Ptr = ThreadCreate(@thread)
Do
Locate 3, 2
Print Space(Len(reply) + 2);
Locate 3, 2
Input reply
Loop Until LCase(reply) = "quit"
Print " Stop the thread"
ScreenCopy
terminate=1
ThreadWait (p)
Print " Thread terminated"
ScreenCopy
Sleep
注意: 'clear the print area' 行の直後の
Sleep x, 1 キーワードは、ダブル・バッファリングが使われていない場合に、ちらつきを強調するためだけにここにあります(Input の使用により画面ロックが禁止されています)。
- 上の例から、表示する日付と表示する時間が、2つの別個のユーザー・スレッドになった場合、これらの 2つのスレッド間の、排他ミューテックス・コード・ブロックだけが必須です。描画命令文自体が競合するからではなく、これら2つのユーザー・スレッドだけを競合させる、ダブル・バッファリング・メソッドが使われている(ちらつきに対する)からです:
#include "vbcompat.bi"
Screen 12, , 2
ScreenSet 1, 0
Color 0, 7
Cls
Dim Shared terminate As Integer = 0
Dim Shared mutex As Any Ptr
Sub thread1 (ByVal param As Any Ptr)
ScreenSet 1, 0
Do
MutexLock(mutex)
Line (16, 432)-Step(96, 16), 11, BF 'clear the print area
Sleep 200, 1
Draw String (24, 432), Format(Now,"dd/mm/yyyy"), 0
ScreenCopy
MutexUnlock(mutex)
Sleep 100, 1
Loop Until terminate = 1
End Sub
Sub thread2 (ByVal param As Any Ptr)
ScreenSet 1, 0
Do
MutexLock(mutex)
Line (16, 448)-Step(96, 16), 11, BF 'clear the print area
Sleep 100, 1
Draw String (32, 448), Format(Now,"hh:mm:ss"), 0
ScreenCopy
MutexUnlock(mutex)
Sleep 100, 1
Loop Until terminate = 1
End Sub
Dim As String reply
Locate 2, 2
Print "Enter ""quit"" to quit"
ScreenCopy
mutex = MutexCreate
Dim p1 As Any Ptr = ThreadCreate(@thread1)
Dim p2 As Any Ptr = ThreadCreate(@thread2)
Do
Locate 3, 2
Print Space(Len(reply) + 2);
Locate 3, 2
Input reply
Loop Until LCase(reply) = "quit"
Print " Stop the threads"
ScreenCopy
terminate=1
ThreadWait (p1)
ThreadWait (p2)
MutexDestroy(mutex)
Print " Threads terminated"
ScreenCopy
Sleep
注意: 'clear the print area' 行の直後の
Sleep x, 1 キーワードは、ダブル・バッファリングが使われていない場合の、ちらつきを強調するためだけに、ここにあります(Input の使用により、画面ロックが禁止されています)。
10. スレッドでキーワード 'Sleep' を使う場合、予防策を講じる方がよいですか?
マルチスレッド文脈で、キーワード
Sleep の完全な動作については、まだ疑問が残っています。
したがって、これを使うときには、次の予防措置を講じることを推奨します:
- スレッドの危険領域(Critical Sections) でどうしても必要な場合は、構文 Sleep x と Sleep x, 0 は、キープレスの内部テストを誘発するため、安全性を最大にするために、他のスレッドとの同時競合を可能な限り回避するため、Inkey キーワードと同じように扱う必要があります。
- それ以外は、相互排除による保護がない場合、構文 Sleep x, 1 (キー押下の内部テストを行わない)が推奨されます。
相互排除は、他のスレッドのタイム・スライスを解放する場合があります。
11. マルチ-スレッドを処理するすべてのツールを、ベース・クラスにカプセル化できますか?(ユーザーが独自の実装のために、派生型で拡張する)
単純な 'threadUDT' ベース・クラスは、次のように定義できます:
- 各ハンドルに、プライベート 'Any Ptr' 非静的メンバー・フィールドを使って、
- 1つの共有ミューテックスの、プライベート 'Any Ptr' 静的メンバー・フィールドを使って、
- 1つの共有条件変数の、プライベート 'Any Ptr' 静的メンバー・フィールドを使って、
- 独自のパブリック・メンバ手続き 'Sub()' を使って、対応するマルチ-スレッド用の、組み込み手続き(同じ手続き名)を呼び出します。上記3つのポインタの値整合性テストも含みます。
(3つの 'thread...()' メンバーSub の非静的手続き、および、4つの 'mutex...()' メンバーSub と 5つの 'cond...()' メンバーSub の静的手続き)、
- ユーザー派生型内から、別の 'Sub()' によって上書きされる、抽象プライベート 'Sub()' スレッドで(したがって、静的アドレスは、オブジェクトの仮想テーブルで利用可能であり、参照により渡される隠された 'This' パラメーターは、スレッドに渡される 'Any Ptr' パラメータと互換性があります)。
#include once "fbthread.bi"
Type threadUDT Extends Object
Public:
Declare Sub ThreadCreate ()
Declare Sub ThreadWait ()
Declare Sub threadDetach ()
Declare Static Sub MutexCreate ()
Declare Static Sub MutexLock ()
Declare Static Sub MutexUnlock ()
Declare Static Sub MutexDestroy ()
Declare Static Sub CondCreate ()
Declare Static Sub CondWait ()
Declare Static Sub CondSignal ()
Declare Static Sub CondBroadcast ()
Declare Static Sub CondDestroy ()
Private:
Declare Abstract Sub thread ()
Dim As Any Ptr pThread
Static As Any Ptr pMutex
Static As Any Ptr pCond
End Type
Dim As Any Ptr threadUDT.pMutex
Dim As Any Ptr threadUDT.pCond
Sub threadUDT.ThreadCreate ()
If This.pThread = 0 Then
This.pThread = .ThreadCreate(Cast(Any Ptr Ptr Ptr, @This)[0][0], @This)
End If
End Sub
Sub threadUDT.ThreadWait ()
If This.pThread > 0 Then
.ThreadWait(This.pThread)
This.pThread = 0
End If
End Sub
Sub threadUDT.threadDetach ()
If This.pThread > 0 Then
.Threaddetach(This.pThread)
This.pThread = 0
End If
End Sub
Sub threadUDT.MutexCreate ()
If threadUDT.pMutex = 0 Then
threadUDT.pMutex = .MutexCreate
End If
End Sub
Sub threadUDT.MutexLock ()
If threadUDT.pMutex > 0 Then
.MutexLock(threadUDT.pMutex)
End If
End Sub
Sub threadUDT.MutexUnlock ()
If threadUDT.pMutex > 0 Then
.MutexUnlock(threadUDT.pMutex)
End If
End Sub
Sub threadUDT.MutexDestroy ()
If threadUDT.pMutex > 0 Then
.MutexDestroy(threadUDT.pMutex)
threadUDT.pMutex = 0
End If
End Sub
Sub threadUDT.CondCreate ()
If threadUDT.pCond = 0 Then
threadUDT.pCond = .CondCreate
End If
End Sub
Sub threadUDT.CondWait ()
If threadUDT.pCond > 0 And threadUDT.pMutex > 0 Then
.CondWait(threadUDT.pCond, threadUDT.pMutex)
End If
End Sub
Sub threadUDT.CondSignal ()
If threadUDT.pCond > 0 And threadUDT.pMutex > 0 Then
.CondSignal(threadUDT.pCond)
End If
End Sub
Sub threadUDT.CondBroadcast ()
If threadUDT.pCond > 0 And threadUDT.pMutex > 0 Then
.CondBroadcast(threadUDT.pCond)
End If
End Sub
Sub threadUDT.CondDestroy ()
If threadUDT.pCond > 0 Then
.CondDestroy(threadUDT.pCond)
threadUDT.pCond = 0
End If
End Sub
- 危険領域(Critical Sections) ページの例 2「すべてのスレッドに対して condwait と condbroadcast (および mutex) を使う同期メソッドの例」から、ユーザー実装は、基本クラス 'threadUDT' と互換性があるように変更されました:
#include once "fbthread.bi"
Type threadUDT Extends Object
Public:
Declare Sub ThreadCreate ()
Declare Sub ThreadWait ()
Declare Sub threadDetach ()
Declare Static Sub MutexCreate ()
Declare Static Sub MutexLock ()
Declare Static Sub MutexUnlock ()
Declare Static Sub MutexDestroy ()
Declare Static Sub CondCreate ()
Declare Static Sub CondWait ()
Declare Static Sub CondSignal ()
Declare Static Sub CondBroadcast ()
Declare Static Sub CondDestroy ()
Private:
Declare Abstract Sub thread ()
Dim As Any Ptr pThread
Static As Any Ptr pMutex
Static As Any Ptr pCond
End Type
Dim As Any Ptr threadUDT.pMutex
Dim As Any Ptr threadUDT.pCond
Sub threadUDT.ThreadCreate ()
If This.pThread = 0 Then
This.pThread = .ThreadCreate(Cast(Any Ptr Ptr Ptr, @This)[0][0], @This)
End If
End Sub
Sub threadUDT.ThreadWait ()
If This.pThread > 0 Then
.ThreadWait(This.pThread)
This.pThread = 0
End If
End Sub
Sub threadUDT.threadDetach ()
If This.pThread > 0 Then
.Threaddetach(This.pThread)
This.pThread = 0
End If
End Sub
Sub threadUDT.MutexCreate ()
If threadUDT.pMutex = 0 Then
threadUDT.pMutex = .MutexCreate
End If
End Sub
Sub threadUDT.MutexLock ()
If threadUDT.pMutex > 0 Then
.MutexLock(threadUDT.pMutex)
End If
End Sub
Sub threadUDT.MutexUnlock ()
If threadUDT.pMutex > 0 Then
.MutexUnlock(threadUDT.pMutex)
End If
End Sub
Sub threadUDT.MutexDestroy ()
If threadUDT.pMutex > 0 Then
.MutexDestroy(threadUDT.pMutex)
threadUDT.pMutex = 0
End If
End Sub
Sub threadUDT.CondCreate ()
If threadUDT.pCond = 0 Then
threadUDT.pCond = .CondCreate
End If
End Sub
Sub threadUDT.CondWait ()
If threadUDT.pCond > 0 And threadUDT.pMutex > 0 Then
.CondWait(threadUDT.pCond, threadUDT.pMutex)
End If
End Sub
Sub threadUDT.CondSignal ()
If threadUDT.pCond > 0 And threadUDT.pMutex > 0 Then
.CondSignal(threadUDT.pCond)
End If
End Sub
Sub threadUDT.CondBroadcast ()
If threadUDT.pCond > 0 And threadUDT.pMutex > 0 Then
.CondBroadcast(threadUDT.pCond)
End If
End Sub
Sub threadUDT.CondDestroy ()
If threadUDT.pCond > 0 Then
.CondDestroy(threadUDT.pCond)
threadUDT.pCond = 0
End If
End Sub
'------------------------------------------------------------------------------
Type UDT Extends threadUDT
Declare Sub counter ()
Declare Sub thread ()
Dim As Integer number
Dim As Integer tempo
Dim As ULongInt count
Static As Integer threadPriorityNumber
Static As Integer numberMax
Static As Integer quit
End Type
Dim As Integer UDT.threadPriorityNumber
Dim As Integer UDT.numberMax
Dim As Integer UDT.quit
Sub UDT.counter ()
Locate This.number, This.number, 0
Sleep 5, 1
This.count += 1
Print This.count;
Locate This.number, 30 + This.number, 0
End Sub
Sub UDT.Thread ()
Dim As Integer myquit
Do
This.MutexLock()
While UDT.threadPriorityNumber <> This.number '' synchronous condwait for expected condition
This.CondWait()
Wend
This.counter()
myquit = UDT.quit
UDT.threadPriorityNumber = (UDT.threadPriorityNumber + 1) Mod (UDT.numberMax + 1)
This.CondBroadcast()
This.MutexUnlock()
Sleep This.tempo, 1
Loop Until myquit = 1
End Sub
UDT.numberMax = 6
Dim As UDT u(0 To UDT.numberMax)
For I As Integer = 0 To UDT.numberMax
u(I).number = i
u(I).tempo = 100 + 15 * I - 95 * Sgn(I)
Next I
UDT.MutexCreate()
UDT.CondCreate()
Dim As Single t = Timer
For I As Integer = 1 To UDT.numberMax
u(I).ThreadCreate()
Next I
Dim As String s
Do
UDT.MutexLock()
While UDT.threadPriorityNumber <> u(0).number
UDT.CondWait()
Wend
s = Inkey
If s <> "" Then
UDT.quit = 1
End If
UDT.threadPriorityNumber = (UDT.threadPriorityNumber + 1) Mod (UDT.numberMax + 1)
UDT.CondBroadcast()
UDT.MutexUnlock()
Sleep u(0).tempo, 1
Loop Until s <> ""
For I As Integer = 1 To UDT.numberMax
u(I).ThreadWait()
Next I
t = Timer - t
UDT.MutexDestroy()
UDT.CondDestroy()
Dim As ULongInt c
For I As Integer = 1 To UDT.numberMax
c += u(I).count
Next I
Locate UDT.numberMax+2, 1
Print CULngInt(c / t) & " increments per second"
Sleep
12. 「ThreadCreate」によってスレッドが作成された後のスレッドのコードの実行遅延はどれくらいですか?
スレッドの最初のコード行は、少なくとも 'ThreadCreate()' が戻った後に常に実行されると考えるかもしれませんが、これは保証されておらず、観察すらされていません。
'ThreadCreate()' の次の行と最初のスレッドコード行の間で可能な限り類似した時間を記憶することによって、'ThreadCreate()' の戻りとスレッド開始の間の遅延(正または負)を推定することができます(遅延計算はスレッドの終了後に実行されます)。
しばらく観察すると、小さな負の値と大きな正の値の両方を見つけることができます。
スレッド本体の実行開始から 'ThreadCreate()' の戻り点までの最小時間、平均時間、最大時間を確認するのは興味深いことです:
Dim As Any Ptr ptid
Dim As Double t0
Dim As Any Ptr p0 = @t0
Dim As Double t1
Dim As Double count
Dim As Single tmean
Dim As Single tmin = 10 '' start value
Dim As Single tmax = -10 '' start value
Sub myThread (ByVal p As Any Ptr)
*Cast(Double Ptr, p) = Timer '' similar code line as in main code
End Sub
Print "Tmin/Tmean/Tmax between begin of thread code and return from ThreadCreate() :"
Do
count += 1
ptid = ThreadCreate(@myThread, @t1)
*Cast(Double Ptr, p0) = Timer '' similar code line as in thread code
ThreadWait(ptid)
tmean = (tmean * (count - 1) + (t1 - t0)) / count
If t1 - t0 < tmin Or t1 - t0 > tmax Then
If t1 - t0 < tmin Then
tmin = t1 - t0
End If
If t1 - t0 > tmax Then
tmax = t1 - t0
End If
Print Time; Using " Tmin=+###.###### ms Tmean=+###.###### ms Tmax=+###.###### ms"; tmin * 1000; tmean * 1000; tmax * 1000
End If
Loop Until Inkey <> ""
出力 (例):
Tmin/Tmean/Tmax between begin of thread code and return from ThreadCreate() :
21:30:13 Tmin= +0.151800 ms Tmean= +0.151800 ms Tmax= +0.151800 ms
21:30:13 Tmin= +0.006000 ms Tmean= +0.078900 ms Tmax= +0.151800 ms
21:30:13 Tmin= +0.006000 ms Tmean= +0.098394 ms Tmax= +0.172500 ms
21:30:13 Tmin= +0.006000 ms Tmean= +0.121555 ms Tmax= +0.884900 ms
21:30:45 Tmin= +0.006000 ms Tmean= +0.055810 ms Tmax= +1.104200 ms
21:30:54 Tmin= +0.006000 ms Tmean= +0.055764 ms Tmax= +4.056600 ms
21:31:44 Tmin= -0.116300 ms Tmean= +0.055516 ms Tmax= +4.056600 ms
21:32:10 Tmin= -0.136800 ms Tmean= +0.057177 ms Tmax= +4.056600 ms
21:32:12 Tmin= -0.150300 ms Tmean= +0.057265 ms Tmax= +4.056600 ms
21:33:17 Tmin= -0.150300 ms Tmean= +0.060048 ms Tmax= +4.979900 ms
21:33:18 Tmin= -0.150300 ms Tmean= +0.060157 ms Tmax= +7.086300 ms
21:33:23 Tmin= -0.150600 ms Tmean= +0.060347 ms Tmax= +7.086300 ms
21:33:38 Tmin= -0.205900 ms Tmean= +0.060878 ms Tmax= +7.086300 ms
21:35:30 Tmin= -0.208700 ms Tmean= +0.061315 ms Tmax= +7.086300 ms
注意:
ユーザーが少なくとも 'ThreadCreate()' 行に続くいくつかのコード行の後、スレッドの実行を常に遅らせたい場合は、'ThreadCreate()' 行とスレッド本体の開始行の間の相互排除を、以下の原則に従って使うことができます:
Dim Shared As Any Ptr pMutexForThreadStart
'-------------------------------------------
Sub Thread (ByVal p As Any Ptr)
MutexLock(pMutexForThreadStart)
MutexUnlock(pMutexForThreadStart)
'
' user thread body
'
End Sub
'--------------------------------------------
'
' user main code
'
pMutexForThreadStart = MutexCreate()
'
' user main code continues
'
MutexLock(pMutexForThreadStart)
Dim As Any Ptr pThread = ThreadCreate(@Thread)
'
' lines of code to be executed before the executing start of the user body of the thread
'
MutexUnlock(pMutexForThreadStart)
'
' user main code continues
'
ThreadWait(pThread)
MutexDestroy(pMutexForThreadStart)
13. 相互排他または条件変数によってスレッドを同期させる場合、同期待ち時間はどのようになりますか?
The synchronization waiting phase of each thread should not consume any CPU resources like 'Sleep', which is the case of 'MutexLock()' and 'CondWait()' instructions.
Thread synchronization by mutual exclusions or by conditional variables adds latency to the initial execution time of the threads, but this latency (a few microseconds) is infinitely shorter than that of a simple simple wait loop (a few milliseconds at best) containing the shortest sleep ('Sleep 1, 1') with a flag test.
The following code allows to estimate this synchronization latency between the main thread and a child thread, by using either simple flags, either mutual exclusions, or conditional variables:
Dim Shared As Any Ptr mutex0, mutex1, mutex2, mutex, cond1, cond2, pt
Dim Shared As Integer flag1, flag2
Dim As Double t
'----------------------------------------------------------------------------------
#if defined(__FB_WIN32__)
Declare Function _setTimer Lib "winmm" Alias "timeBeginPeriod"(ByVal As Ulong = 1) As Long
Declare Function _resetTimer Lib "winmm" Alias "timeEndPeriod"(ByVal As Ulong = 1) As Long
#endif
Sub ThreadFlag(ByVal p As Any Ptr)
MutexUnlock(mutex0) '' unlock mutex for main thread
For I As Integer = 1 To 100
While flag1 = 0
Sleep 1, 1
Wend
flag1 = 0
' only child thread code runs (location for example)
flag2 = 1
Next I
End Sub
mutex0 = MutexCreate()
MutexLock(mutex0)
pt = ThreadCreate(@ThreadFlag)
MutexLock(mutex0) '' wait for thread launch (mutex unlock from child thread)
Print "Thread synchronization latency by simple flags:"
#if defined(__FB_WIN32__)
_setTimer()
Print "(in high resolution OS cycle period)"
#else
Print "(in normal resolution OS cycle period)"
#endif
t = Timer
For I As Integer = 1 To 100
flag1 = 1
While flag2 = 0
Sleep 1, 1
Wend
flag2 = 0
' only main thread code runs (location for example)
Next I
t = Timer - t
#if defined(__FB_WIN32__)
_resetTimer()
#endif
ThreadWait(pt)
Print Using "####.## milliseconds per double synchronization (round trip)"; t * 10
Print
MutexDestroy(mutex0)
'----------------------------------------------------------------------------------
Sub ThreadMutex(ByVal p As Any Ptr)
MutexUnlock(mutex0) '' unlock mutex for main thread
For I As Integer = 1 To 100000
MutexLock(mutex1) '' wait for mutex unlock from main thread
' only child thread code runs
MutexUnlock(mutex2) '' unlock mutex for main thread
Next I
End Sub
mutex0 = MutexCreate()
mutex1 = MutexCreate()
mutex2 = MutexCreate()
MutexLock(mutex0)
MutexLock(mutex1)
MutexLock(mutex2)
pt = ThreadCreate(@ThreadMutex)
MutexLock(mutex0) '' wait for thread launch (mutex unlock from child thread)
Print "Thread synchronization latency by mutual exclusions:"
t = Timer
For I As Integer = 1 To 100000
MutexUnlock(mutex1) '' mutex unlock for child thread
MutexLock(mutex2) '' wait for mutex unlock from child thread
' only main thread code runs
Next I
t = Timer - t
ThreadWait(pt)
Print Using "####.## microseconds per double synchronization (round trip)"; t * 10
Print
MutexDestroy(mutex0)
MutexDestroy(mutex1)
MutexDestroy(mutex2)
'----------------------------------------------------------------------------------
Sub ThreadCondVar(ByVal p As Any Ptr)
MutexUnlock(mutex0) '' unlock mutex for main thread
For I As Integer = 1 To 100000
MutexLock(mutex)
While flag1 = 0
CondWait(cond1, mutex) '' wait for conditional signal from main thread
Wend
flag1 = 0
' only child thread code runs (location for example)
flag2 = 1
CondSignal(cond2) '' send conditional signal to main thread
MutexUnlock(mutex)
Next I
End Sub
mutex0 = MutexCreate()
mutex = MutexCreate()
MutexLock(mutex0)
cond1 = CondCreate()
cond2 = CondCreate()
pt = ThreadCreate(@ThreadCondVar)
MutexLock(mutex0) '' wait for thread launch (mutex unlock from child thread)
Print "Thread synchronization latency by conditional variables:"
t = Timer
For I As Integer = 1 To 100000
MutexLock(mutex)
flag1 = 1
CondSignal(cond1) '' send conditional signal to main thread
While flag2 = 0
CondWait(Cond2, mutex) '' wait for conditional signal from child thread
Wend
flag2 = 0
' only child thread code runs (location for example)
MutexUnlock(mutex)
Next I
t = Timer - t
ThreadWait(pt)
Print Using "####.## microseconds per double synchronization (round trip)"; t * 10
Print
MutexDestroy(mutex0)
MutexDestroy(mutex)
CondDestroy(cond1)
CondDestroy(cond2)
'----------------------------------------------------------------------------------
Sleep
Example of results:
Thread synchronization latency by simple flags:
(in high resolution OS cycle period)
2.02 milliseconds per double synchronization (round trip)
Thread synchronization latency by mutual exclusions:
5.93 microseconds per double synchronization (round trip)
Thread synchronization latency by conditional variables:
7.54 microseconds per double synchronization (round trip)
Example of thread synchronization for executing in concurrent or exclusive mode by using conditional variable:
Dim Shared As Any Ptr pt, mutex1, mutex2, cond1, cond2
Dim Shared As Integer quit, flag1, flag2
Print "'1': Main thread procedure running (alone)"
Print "'2': Child thread procedure running (alone)"
Print "'-': Main thread procedure running (with the one of child thread)"
Print "'=': Child thread procedure running (with the one of main thread)"
Print
Sub Prnt(ByRef s As String, ByVal n As Integer)
For I As Integer = 1 To n
Print s;
Sleep 20, 1
Next I
End Sub
Sub ThreadCondCond(ByVal p As Any Ptr)
Do
MutexLock(mutex1)
While flag1 = 0 '' test flag set from main thread
CondWait(cond1, mutex1) '' wait for conditional signal from main thread
Wend
flag1 = 0 '' reset flag
MutexUnlock(mutex1)
If quit = 1 Then Exit Sub '' exit the threading loop
Prnt("=", 10)
MutexLock(mutex2)
flag2 = 1 '' set flag to main thread
CondSignal(cond2) '' send conditional signal to main thread
Prnt("2", 10)
MutexUnlock(mutex2)
Loop
End Sub
mutex1 = MutexCreate()
mutex2 = MutexCreate()
cond1 = CondCreate()
cond2 = CondCreate()
pt = ThreadCreate(@ThreadCondCond)
For I As Integer = 1 To 5
MutexLock(mutex1)
flag1 = 1 '' set flag to child thread
CondSignal(cond1) '' send conditional signal to child thread
MutexUnlock(mutex1)
Prnt("-", 10)
MutexLock(mutex2)
While flag2 = 0 '' test flag set from child thread
CondWait(Cond2, mutex2) '' wait for conditional signal from child thread
Wend
flag2 = 0 '' reset flag
Prnt("1", 10)
MutexUnlock(mutex2)
Next I
MutexLock(mutex1)
quit = 1 '' set quit for child thread
flag1 = 1
CondSignal(cond1) '' send conditional signal to child thread
MutexUnlock(mutex1)
ThreadWait(pt) '' wait for child thread to end
Print
MutexDestroy(mutex1)
MutexDestroy(mutex2)
CondDestroy(cond1)
CondDestroy(cond2)
Sleep
Output:
'1': Main thread procedure running (alone)
'2': Child thread procedure running (alone)
'-': Main thread procedure running (with the one of child thread)
'=': Child thread procedure running (with the one of main thread)
-==-=-=--==--==-=-=-22222222221111111111-=-=-=-==--==-=--==-22222222221111111111-=-=-==-=-=--=-=-==-22222222221111111111
-=-=-=-=-=-=--==-=-=22222222221111111111-==--==--==-=-=--==-22222222221111111111
14. 複数のスレッドが同じ条件変数で待機している場合はどうなりますか?
- If 'CondSignal()' is used:
The specification states that the thread scheduler arbitrarily notifies one of the threads waiting on the condition variable.
Since this method notifies an arbitrary thread, some documentation also states that it is therefore possible for a waiting thread to never be notified if it is never alone in the queue on the condition variable.
But my tests on Windows seem to show that the order of entry into the queue on a same condition variable determines the order of exit: first in, first out.
I have not tested this on Linux or any other platform, but in any case this behavior I observed is unspecified.
- If 'CondBroadcast()' is used:
The specification states that the thread scheduler notifies all threads waiting on the condition variable in an arbitrary order.
The example below works with 6 threads (in addition to the main thread).
The first 3 threads (#1 to #3) are waiting on their own condition variable, while the last 3 threads (#4 to #6) are waiting on a same other condition variable.
These last 3 threads are awakened either on 'CondSignal()' or on 'CondBroadcast()'.
Type ThreadData
Dim As Integer id
Dim As Any Ptr mutex
Dim As Any Ptr cond
Dim As Boolean flag
Dim As Boolean quit
Dim As Any Ptr handle
Declare Static Sub Thread(ByVal p As Any Ptr)
End Type
Sub ThreadData.Thread(ByVal p As Any Ptr)
Dim As ThreadData Ptr pdata = p
Print " thread #" & pdata->id & " is running"
Do
MutexLock(pdata->mutex)
While pdata->flag = False
CondWait(pdata->cond, pdata->mutex)
Wend
pdata->flag = False
MutexUnlock(pdata->mutex)
If pdata->quit = False Then
Print " thread #" & pdata->id & " is signaled"
Else
Exit Do
End If
Loop
Print " thread #" & pdata->id & " is finishing"
End Sub
Dim As Any Ptr mutex = MutexCreate()
Dim As Any Ptr cond(0 To 3) = {CondCreate(), CondCreate(), CondCreate(), CondCreate()}
Dim As ThreadData mythreads(1 To 6) = {Type(1, mutex, cond(1)), Type(2, mutex, cond(2)), Type(3, mutex, cond(3)), _
Type(4, mutex, cond(0)), Type(5, mutex, cond(0)), Type(6, mutex, cond(0))}
Print "Threads from #1 to #6 are created:"
For I As Integer = LBound(mythreads) To UBound(mythreads)
mythreads(I).handle = ThreadCreate(@ThreadData.Thread, @mythreads(I))
Next I
Sleep 1000, 1 '' wait for all threads started
Print
Print "----------------------------------------------------------"
Print
For I As Integer = 3 To 1 Step -1
Print "Send a CondSignal to thread #" & I &":"
MutexLock(mutex)
mythreads(I).flag = True
CondSignal(cond(I))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for the thread loop completed
Print
Next I
Print "----------------------------------------------------------"
Print
Print "Send a single CondBroadcast to all threads from #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondBroadcast(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for all thread loops completed
Print "Send a single CondBroadcast to all threads from #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondBroadcast(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for all thread loops completed
Print "Send a single CondBroadcast to all threads from #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondBroadcast(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for all thread loops completed
Print
Print "----------------------------------------------------------"
Print
Print "Send a CondSignal to any thread among #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondSignal(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for a thread loop completed
Print "Send a CondSignal to any thread among #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondSignal(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for a thread loop completed
Print "Send a CondSignal to any thread among #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondSignal(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for a thread loop completed
Print
Print "Send a CondSignal to any thread among #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondSignal(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for a thread loop completed
Print "Send a CondSignal to any thread among #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondSignal(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for a thread loop completed
Print "Send a CondSignal to any thread among #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondSignal(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for a thread loop completed
Print
Print "Send a CondSignal to any thread among #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondSignal(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for a thread loop completed
Print "Send a CondSignal to any thread among #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondSignal(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for a thread loop completed
Print "Send a CondSignal to any thread among #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
Next I
CondSignal(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for a thread loop completed
Print
Print "----------------------------------------------------------"
Print
For I As Integer = 1 To 3
Print "Send to finish a CondSignal to thread #" & I &":"
MutexLock(mutex)
mythreads(I).flag = True
mythreads(I).quit = True
CondSignal(cond(I))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for the thread loop completed
Print
Next I
Print "----------------------------------------------------------"
Print
Print "Send to finish a single CondBroadcast to all threads from #4 to #6:"
MutexLock(mutex)
For I As Integer = 4 To 6
mythreads(I).flag = True
mythreads(I).quit = True
Next I
CondBroadcast(cond(0))
MutexUnlock(mutex)
Sleep 1000, 1 '' wait for all thread loops completed
Print
Print "----------------------------------------------------------"
Print
For I As Integer = 1 To 3
ThreadWait(mythreads(I).handle)
CondDestroy(cond(I))
Next I
For I As Integer = 4 To 6
ThreadWait(mythreads(I).handle)
Next I
Print "All threads from #1 to #6 are finished."
Print
MutexDestroy(mutex)
CondDestroy(cond(0))
Sleep
Output (for example):
Threads from #1 to #6 are created:
thread #1 is running
thread #3 is running
thread #2 is running
thread #5 is running
thread #4 is running
thread #6 is running
----------------------------------------------------------
Send a CondSignal to thread #3:
thread #3 is signaled
Send a CondSignal to thread #2:
thread #2 is signaled
Send a CondSignal to thread #1:
thread #1 is signaled
----------------------------------------------------------
Send a single CondBroadcast to all threads from #4 to #6:
thread #5 is signaled
thread #6 is signaled
thread #4 is signaled
Send a single CondBroadcast to all threads from #4 to #6:
thread #6 is signaled
thread #4 is signaled
thread #5 is signaled
Send a single CondBroadcast to all threads from #4 to #6:
thread #5 is signaled
thread #4 is signaled
thread #6 is signaled
----------------------------------------------------------
Send a CondSignal to any thread among #4 to #6:
thread #5 is signaled
Send a CondSignal to any thread among #4 to #6:
thread #4 is signaled
Send a CondSignal to any thread among #4 to #6:
thread #6 is signaled
Send a CondSignal to any thread among #4 to #6:
thread #5 is signaled
Send a CondSignal to any thread among #4 to #6:
thread #4 is signaled
Send a CondSignal to any thread among #4 to #6:
thread #6 is signaled
Send a CondSignal to any thread among #4 to #6:
thread #5 is signaled
Send a CondSignal to any thread among #4 to #6:
thread #4 is signaled
Send a CondSignal to any thread among #4 to #6:
thread #6 is signaled
----------------------------------------------------------
Send to finish a CondSignal to thread #1:
thread #1 is finishing
Send to finish a CondSignal to thread #2:
thread #2 is finishing
Send to finish a CondSignal to thread #3:
thread #3 is finishing
----------------------------------------------------------
Send to finish a single CondBroadcast to all threads from #4 to #6:
thread #4 is finishing
thread #5 is finishing
thread #6 is finishing
----------------------------------------------------------
All threads from #1 to #6 are finished.
15. スレッドによって実行される連続したユーザー・タスクの順序を最適化する方法は?
The delay between the return of 'ThreadCreate()' and the start of the thread code (first line of thread code) can be estimated at about 50 microseconds on average, but can go up to a few milliseconds at worst.
(see FAQ
12. What is the execution delay of the code of a thread after the thread is created by 'ThreadCreate'?)
This is why a child thread can be launched only once (by a constructor for example) and execute a permanent waiting loop of user tasks (to avoid a thread launch latency each time), then at end stopped (by a destructor).
The synchronization between the main thread and the child thread (start of each user task and user task completed) can be managed by means of 2 mutexes.
Example to estimate the average time to execute a sequence of user tasks (with empty user procedure body):
- either launched by successive threads,
- or launched by a single thread.
Sub userTask(ByVal p As Any Ptr) '' task to execute
End Sub
Dim As Double t
'---------------------------------------------------------------------------
Print "Successive (empty) user tasks executed by one thread for each:"
t = Timer
For i As Integer = 1 To 10000
Dim As Any Ptr p = ThreadCreate(@userTask)
ThreadWait(p)
Next i
t = Timer - t
Print Using "######.### microdeconds per user task"; t * 100
Print
'---------------------------------------------------------------------------
Type thread
Public:
Dim As Sub(ByVal p As Any Ptr) task '' pointer to user task
Declare Sub Launch() '' launch user task
Declare Sub Wait() '' wait for user task completed
Declare Constructor()
Declare Destructor()
Private:
Dim As Any Ptr mutex1
Dim As Any Ptr mutex2
Dim As Any Ptr handle
Dim As Boolean quit
Declare Static Sub proc(ByVal pthread As thread Ptr)
End Type
Constructor thread()
This.mutex1 = MutexCreate
This.mutex2 = MutexCreate
MutexLock(This.mutex1)
MutexLock(This.mutex2)
This.handle = ThreadCreate(CPtr(Any Ptr, @thread.proc), @This)
End Constructor
Destructor thread()
This.quit = True
MutexUnlock(This.mutex1)
ThreadWait(This.handle)
MutexDestroy(This.mutex1)
MutexDestroy(This.mutex2)
End Destructor
Sub thread.proc(ByVal pthread As thread Ptr)
Do
MutexLock(pthread->mutex1) '' wait for launching task
If pthread->quit = True Then Exit Sub
pthread->task(pthread)
MutexUnlock(pthread->mutex2) '' task completed
Loop
End Sub
Sub thread.Launch()
MutexUnlock(This.mutex1)
End Sub
Sub thread.Wait()
MutexLock(This.mutex2)
End Sub
Print "Successive (empty) user tasks executed by a single thread for all:"
t = Timer
Dim As thread Ptr pThread = New Thread
pThread->task = @userTask
For i As Integer = 1 To 10000
pThread->Launch()
pThread->Wait()
Next i
Delete pThread
t = Timer - t
Print Using "######.### microdeconds per user task"; t * 100
Print
Sleep
Output (for example):
Successive (empty) user tasks executed by one thread for each:
145.004 microdeconds per user task
Successive (empty) user tasks executed by a single thread for all:
6.691 microdeconds per user task
16. 共有メモリ アクセスが多数あると、マルチスレッドのパフォーマンスが低下するのはなぜですか (書き込みモードではさらに低下します)?
Each core has its own cache memory that allows to buffer the useful data (in read and write) of the shared memory.
Consequently, a cache coherence algorithm between cores is executed, to keep, in case of writing in the cache and for the common memory areas between caches, the most recent values ??among all the caches concerned.
It is this algorithm which penalizes the performance of multi-threading in the case of multiple accesses in shared memory, even more particularly in write mode.
It is therefore necessary to limit as much as possible the access of threads to shared memory, even more in writing.
For example, all intermediate results of threads could be performed in local memory, and only the final useful ones put in shared memory.
Example of a member thread procedures computing the sum of the first N integers, by accumulation directly in the shared memory ('SumUpTo_1()') or internally in its local memory before copy back ('SumUpTo_2'()'):
Type Thread
Dim As UInteger valueIN
Dim As Double valueOUT
Dim As Any Ptr pHandle
Declare Static Sub SumUpTo_1(ByVal pt As Thread Ptr)
Declare Static Sub SumUpTo_2(ByVal pt As Thread Ptr)
End Type
Sub Thread.SumUpTo_1(ByVal pt As Thread Ptr)
pt->valueOut = 0
For I As UInteger = 1 To pt->valueIN
pt->valueOUT += I
Next I
End Sub
Sub Thread.SumUpTo_2(ByVal pt As Thread Ptr)
Dim As Double value = 0
For I As UInteger = 1 To pt->valueIN
value += I
Next I
pt->valueOUT = value
End Sub
Sub MyThreads(ByVal pThread As Any Ptr, ByVal threadNB As UInteger = 1)
Dim As Thread td(1 To threadNB)
Dim As Double t
t = Timer
For i As Integer = 1 To threadNB
td(i).valueIN = 100000000 + i
td(i).pHandle = ThreadCreate(pThread, @td(i))
Next I
For i As Integer = 1 To threadNB
ThreadWait(td(i).pHandle)
Next I
t = Timer - t
For i As Integer = 1 To threadNB
Print " SumUpTo(" & td(i).valueIN & ") = " & td(i).valueOUT, _
"(right result : " & (100000000# + i) * (100000000# + i + 1) / 2 & ")"
Next I
Print " total time : " & t & " s"
Print
End Sub
Print
For i As Integer = 1 To 4
Print "Each thread (in parallel) accumulating result directly in shared memory:"
Mythreads(@Thread.SumUpTo_1, I)
Print "Each thread (in parallel) accumulating result internally in its local memory:"
Mythreads(@Thread.SumUpTo_2, I)
Print "-----------------------------------------------------------------------------"
Print
Next i
Sleep
Output (for example):
Each thread (in parallel) accumulating result directly in shared memory:
SumUpTo(100000001) = 5000000150000001 (right result : 5000000150000001)
total time : 1.668927300015184 s
Each thread (in parallel) accumulating result internally in its local memory:
SumUpTo(100000001) = 5000000150000001 (right result : 5000000150000001)
total time : 1.004467599958389 s
-----------------------------------------------------------------------------
Each thread (in parallel) accumulating result directly in shared memory:
SumUpTo(100000001) = 5000000150000001 (right result : 5000000150000001)
SumUpTo(100000002) = 5000000250000003 (right result : 5000000250000003)
total time : 4.314032700025791 s
Each thread (in parallel) accumulating result internally in its local memory:
SumUpTo(100000001) = 5000000150000001 (right result : 5000000150000001)
SumUpTo(100000002) = 5000000250000003 (right result : 5000000250000003)
total time : 1.032165899962706 s
-----------------------------------------------------------------------------
Each thread (in parallel) accumulating result directly in shared memory:
SumUpTo(100000001) = 5000000150000001 (right result : 5000000150000001)
SumUpTo(100000002) = 5000000250000003 (right result : 5000000250000003)
SumUpTo(100000003) = 5000000350000006 (right result : 5000000350000006)
total time : 6.727616399944395 s
Each thread (in parallel) accumulating result internally in its local memory:
SumUpTo(100000001) = 5000000150000001 (right result : 5000000150000001)
SumUpTo(100000002) = 5000000250000003 (right result : 5000000250000003)
SumUpTo(100000003) = 5000000350000006 (right result : 5000000350000006)
total time : 1.128656100041894 s
-----------------------------------------------------------------------------
Each thread (in parallel) accumulating result directly in shared memory:
SumUpTo(100000001) = 5000000150000001 (right result : 5000000150000001)
SumUpTo(100000002) = 5000000250000003 (right result : 5000000250000003)
SumUpTo(100000003) = 5000000350000006 (right result : 5000000350000006)
SumUpTo(100000004) = 5000000450000010 (right result : 5000000450000010)
total time : 6.829728199980309 s
Each thread (in parallel) accumulating result internally in its local memory:
SumUpTo(100000001) = 5000000150000001 (right result : 5000000150000001)
SumUpTo(100000002) = 5000000250000003 (right result : 5000000250000003)
SumUpTo(100000003) = 5000000350000006 (right result : 5000000350000006)
SumUpTo(100000004) = 5000000450000010 (right result : 5000000450000010)
total time : 1.164915200012842 s
-----------------------------------------------------------------------------
One can check that the multi-threading performance is strongly penalized by many shared memory accesses in write mode:
For the case where the thread accumulates the result in shared memory, there is no longer any gain from multi-threading (and even a little loss), whereas for the case where the thread accumulates the result in internal memory, the gain is almost at the theoretical maximum value.
On the other hand, we observe a smaller degradation in multi-threading performance when accessing shared memory in read-only mode.
17. var-len文字列やvar-len配列の操作が多いと、マルチスレッド性能が大きく損なわれるのはなぜですか?
For all pseudo-objects like var-len strings and var-len arrays, only the descriptors can be put into local memory but not the data itself which is always on the heap (only fixed-length data can be put into local memory).
The heap is shared memory and this incurs penality on multi-threading performance as described in the above FAQ.
(see FAQ
16. 共有メモリ アクセスが多数あると、マルチスレッドのパフォーマンスが低下するのはなぜですか (書き込みモードではさらに低下します)?)
For var-len arrays, local fixed-len arrays can be used instead, since this array data is always placed in local scope memory.
Not only do you have to define a fixed maximum size to allocate for each array, but for each of them you have to associate an index variable (per dimension if necessary) that points to the last useful element ('Redim' is replaced by updating this index variable).
For var-len strings, local fix-len [z]strings can be used instead, since these [z]string data are always placed in local scope memory.
All built-in string functions except 'Len()' and 'Asc()' and all string operators should also not be used on [z]strings since they work internally with var-len strings. Instead, use user code that only works on [z]string indexes.
Note:
But fix-len strings ('Dim As String * N') are less convenient to use than fix-len zstrings ('Dim As Zstring * N'), because the former cannot be passed by reference to a procedure (but only by copy), unlike the latter ('Byref As Zstring').
Additionally, all dynamic memory allocation/reallocation/deallocation requests (to be thread-safe) are serialized internally using mutex locking and unlocking.
The following example compares the multithreaded performance of two types of code:
- code with var-len strings using its built-in functions and operators like '= (assign)', 'Instr()', 'Mid()' and 'Ucase()',
- code with fix-len zstrings with user code equivalent to the previous built-in functions and operators, but operating only on zstring indexes.
("Asc()" and "Len()" are the only ones used because they have no impact on performance)
Type Thread
Dim As UInteger value
Dim As Any Ptr pHandle
Declare Static Sub thread1(ByVal pt As Thread Ptr)
Declare Static Sub thread2(ByVal pt As Thread Ptr)
End Type
Sub Thread.thread1(ByVal pt As Thread Ptr)
Dim As Integer result
For n As Integer = 1 To pt->value
Dim As String s1
Dim As String s2
Dim As String s3
s1 = "FreeBASIC rev 1.20"
result = InStr(s1, "rev")
s2 = Mid(s1, result)
s3 = UCase(s2)
Next n
End Sub
Sub Thread.thread2(ByVal pt As Thread Ptr)
Dim As Integer result
For n As Integer = 1 To pt->value
Dim As ZString * 256 z1
Dim As ZString * 256 z2
Dim As ZString * 256 z3
' instead of: z1 = "FreeBASIC rev 1.20"
For i As Integer = 0 To Len("FreeBASIC rev 1.20")
z1[i] = ("FreeBASIC rev 1.20")[i]
Next i
' instead of: result = Instr(z1, "rev")
result = 0
For i As Integer = 0 To Len(z1) - Len("rev")
For j As Integer = 0 To Len("rev") - 1
If z1[i + j] <> ("rev")[j] Then Continue For, For
Next j
result = i + 1
Exit For
Next i
' instead of: z2 = Mid(z1, result)
For i As Integer = result - 1 To Len(z1)
z2[i - result + 1] = z1[i]
Next i
' instead of: z3 = Ucase(z2)
For i As Integer = 0 To Len(z2)
z3[i] = z2[i]
If z3[i] >= Asc("a") Andalso z3[i] <= Asc("z") Then z3[i] -= 32
Next i
Next n
End Sub
Sub MyThreads(ByVal pThread As Any Ptr, ByVal threadNB As UInteger = 1)
Dim As Thread td(1 To threadNB)
Dim As Double t
t = Timer
For i As Integer = 1 To threadNB
td(i).value = 100000
td(i).pHandle = ThreadCreate(pThread, @td(i))
Next I
For i As Integer = 1 To threadNB
ThreadWait(td(i).pHandle)
Next I
t = Timer - t
Print " total time for " & threadNB & " threads in parallel: " & t & " s"
Print
End Sub
Print
For i As Integer = 1 To 8
Print "Each thread using var-len strings, with its built-in functions and operators:"
Mythreads(@Thread.thread1, I)
Print "Each thread using fix-len zstrings, with user code working on zstring indexes:"
Mythreads(@Thread.thread2, I)
Print "------------------------------------------------------------------------------"
Print
Next i
Sleep
Output (for example):
Each thread using var-len strings, with its built-in functions and operators:
total time for 1 threads in parallel: 0.08449090004432946 s
Each thread using fix-len zstrings, with user code working on zstring indexes:
total time for 1 threads in parallel: 0.02201449999120086 s
------------------------------------------------------------------------------
Each thread using var-len strings, with its built-in functions and operators:
total time for 2 threads in parallel: 0.1947050000308082 s
Each thread using fix-len zstrings, with user code working on zstring indexes:
total time for 2 threads in parallel: 0.02090729994233698 s
------------------------------------------------------------------------------
Each thread using var-len strings, with its built-in functions and operators:
total time for 3 threads in parallel: 0.3338784999214113 s
Each thread using fix-len zstrings, with user code working on zstring indexes:
total time for 3 threads in parallel: 0.0279372000368312 s
------------------------------------------------------------------------------
Each thread using var-len strings, with its built-in functions and operators:
total time for 4 threads in parallel: 0.4927077000029385 s
Each thread using fix-len zstrings, with user code working on zstring indexes:
total time for 4 threads in parallel: 0.02361949998885393 s
------------------------------------------------------------------------------
Each thread using var-len strings, with its built-in functions and operators:
total time for 5 threads in parallel: 0.7089884000597522 s
Each thread using fix-len zstrings, with user code working on zstring indexes:
total time for 5 threads in parallel: 0.02638950000982732 s
------------------------------------------------------------------------------
Each thread using var-len strings, with its built-in functions and operators:
total time for 6 threads in parallel: 0.9172402999829501 s
Each thread using fix-len zstrings, with user code working on zstring indexes:
total time for 6 threads in parallel: 0.0310587000567466 s
------------------------------------------------------------------------------
Each thread using var-len strings, with its built-in functions and operators:
total time for 7 threads in parallel: 1.159198799985461 s
Each thread using fix-len zstrings, with user code working on zstring indexes:
total time for 7 threads in parallel: 0.02898070006631315 s
------------------------------------------------------------------------------
Each thread using var-len strings, with its built-in functions and operators:
total time for 8 threads in parallel: 1.403980100061744 s
Each thread using fix-len zstrings, with user code working on zstring indexes:
total time for 8 threads in parallel: 0.03312029992230237 s
------------------------------------------------------------------------------
One can check that the multi-threading performance is strongly penalized by many var-len string manipulation:
For the case where the thread uses var-len strings and its built-in functions and operators, there is no longer any gain from multi-threading (and even losses), whereas for the case where the thread uses fix-len zstrings and user code working on zstring indexes only (except 'Asc()' and 'Len()' usage), the gain is almost at the theoretical maximum value.
参照