他のスレッドとの並行性を処理するための
危険領域(Critical Sections) で、組み込み手続きを適切に使います。
序文:
危険領域(Critical Section)は、原子動作(atomic action)として実行する必要がある、マルチス-レッド・プログラムの一部です(同様の動作を行う他のスレッドとの同時性はありません):
- これは、アクセスの相互排除を必要とする、プログラムの一部です。
- 通常、危険領域(Critical Section)は、複数の同時アクセスを許可しない、データ構造、周辺機器、ネットワーク接続などの、共有リソースにアクセスします。
プログラムが起動すると、すぐに 1つのスレッドが実行を開始します。
これは通常、プログラムの「メイン」スレッドと呼ばれます。プログラムの開始時に実行されるスレッドだからです:
- これは、ユーザーが、他の「子」スレッドを生成するスレッドです(スレッドは他の「サブ-子」スレッドを生成する場合もあります)。
- 多くの場合、これは、さまざまなシャットダウン動作を実行するため、実行を終了する最後のスレッドである必要があります(「子」スレッドは、最終的に生成される「サブ子」スレッドに関しても、そうする必要があります)。
- しかし、それ以外にも、ユーザーが明示的に生成した他のすべてのスレッドと(独自の危険領域(Critical Sections)と)競合することができます。
基本的なアルゴリズム
FreeBASIC が提供する、組み込み手続きを使って、スレッドに適用される、非同期または同期のアルゴリズムで、危険領域(Critical Section)の、排他的使用を保証する方法を、設計できます。
- ミューテックスを使った非同期メソッドの、基本アルゴリズム:
相互排他ロックを取得することにより、スレッドは、排他制御を取得して、共有リソースにアクセスできます。
共有リソースアクセスが終了すると、スレッドは、ミューテックスのロックを解除します。
アルゴリズム:
' Mutexlock
' |
' Critical section of code
' |
' Mutexunlock
- condwait を、そして condsignal/condbroadcast(および mutex)を使う、同期メソッドのための、基本アルゴリズム:
スレッドは、ブール述語が実際に真になり、危険領域(Critical Section)を実行する前の条件信号(condwait)も、待機します。
共有リソース・アクセスが終了すると、スレッドは、別のブール述語を設定し、そして、他のスレッドに条件信号(condsignal か condbroadcast)を送信します。
アルゴリズム:
' Mutexlock
' |
' While my_predicate <> True
' | Condwait
' Wend
' my_predicate = False
' |
' Critical section of code
' |
' other_predicate = True
' Condsignal or Condbroadcast
' |
' Mutexunlock
waiting-for を使った同様のアルゴリズムで、信号伝達は、逆の順序で配置されます:
' Mutexlock
' |
' Critical section of code
' |
' other_predicate = True
' Condsignal or Condbroadcast
' |
' While my_predicate <> True
' | Condwait
' Wend
' my_predicate = False
' |
' Mutexunlock
例
次の2つの例では、共有リソースは、入/出力ディスプレイデバイスです:
- 6つのユーザー・スレッドごとに、カウンターを出力します(フラグ 'quit' を読み取ります)。
- メイン・スレッドのキー-押し(何か)を取得します(そうなら、フラグ 'quit' を 'true' に設定)。
出力手続き(
'Sub Counter()')は、重要なセクションの実行間に、重複がないことを徹底的に確認するために、カーソルの配置と印刷の間で自発的にテンポを持ちます。また、重要なセクションの実行の間に重複がないことを徹底的にチェックするために、終了前に行の中央にテキスト・カーソルを再配置します。
(逆に、相互除外処理専用のコードを削除すると、結果を見ることができます)。
各スレッド・ループの終わりに、異なるテンポ値が設定されます(小さい方から大きい方へ)。
構造(UDT)は、スレッドに必要なすべての変数を、グループ化します。
各 UDT インスタンスへのポインタは、作成フェーズで(threadcreate を使って)各スレッドに渡されます。
- すべてのスレッドに 1つの mutex を使う、非同期メソッドの例:
' User thread algorithm (same principle for the main thread):
'
' 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.
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;
Locate .number, 30 + .number, 0
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
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
Dim As String s
Do
MutexLock(UDT.pMutex)
s = Inkey
If s <> "" Then
UDT.quit = 1
End If
MutexUnlock(UDT.pMutex)
Sleep u(0).tempo, 1
Loop Until s <> ""
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+2, 1
Print CULngInt(c / t) & " increments per second"
Sleep
出力例:
' 159
' 127
' 105
' 85
' 78
' 71
'
' 63 increments per second
非同期カウント(テンポ値の増加でカウント値が減少)による、異なる結果値。
- すべてのスレッドに対して、condwait を使ってから condbroadcast(および1つの mutex)する、同期メソッドの例:
' User thread algorithm (same principle for the main thread):
'
' Do
' |
' | Mutexlock
' | |
' | While thread_priority_number <> my_number
' | | Condwait
' | Wend
' | |
' | Critical section of code
' | |
' | thread_priority_number = next thread_priority_number
' | Condbroadcast
' | |
' | Mutexunlock
' | |
' | Sleep my_tempo, 1
' |
' Loop Until quit = true
'
' The critical sections of the threads are run synchronously one after the other, with a predefined order.
Type UDT
Dim As Integer number
Dim As Integer tempo
Dim As Any Ptr pThread
Dim As ULongInt count
Static As Integer threadPriorityNumber
Static As Any Ptr pMutex
Static As Any Ptr pCond
Static As Integer numberMax
Static As Integer quit
End Type
Dim As Integer UDT.threadPriorityNumber
Dim As Any Ptr UDT.pMutex
Dim As Any Ptr UDT.pCond
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;
Locate .number, 30 + .number, 0
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)
While .threadPriorityNumber <> .number '' synchronous condwait for expected condition
CondWait(.pCond, .pMutex)
Wend
Counter(pUDT)
myquit = .quit
.threadPriorityNumber = (.threadPriorityNumber + 1) Mod (.numberMax + 1)
CondBroadcast(.pCond)
MutexUnlock(.pMutex)
Sleep .tempo, 1
Loop Until myquit = 1
End With
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.pMutex = MutexCreate
UDT.PCond = CondCreate
Dim As Single t = Timer
For I As Integer = 1 To UDT.numberMax
u(I).pThread = ThreadCreate(@Thread, @u(I))
Next I
Dim As String s
Do
MutexLock(UDT.pMutex)
While UDT.threadPriorityNumber <> u(0).number
CondWait(UDT.pCond, UDT.pMutex)
Wend
s = Inkey
If s <> "" Then
UDT.quit = 1
End If
UDT.threadPriorityNumber = (UDT.threadPriorityNumber + 1) Mod (UDT.numberMax + 1)
CondBroadcast(UDT.pCond)
MutexUnlock(UDT.pMutex)
Sleep u(0).tempo, 1
Loop Until s <> ""
For I As Integer = 1 To UDT.numberMax
ThreadWait(u(I).pThread)
Next I
t = Timer - t
MutexDestroy(UDT.pMutex)
CondDestroy(UDT.pCond)
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
出力例:
' 116
' 116
' 116
' 116
' 116
' 116
'
' 49 increments per second
同期カウント(テンポ値が増加しているのに)なので、同じ結果値。
- セルフロックし相互ロックは解除することで、各スレッドにミューテックスを使った、奇妙な同期アルゴリズム:
- 1つのスレッドが、危険領域(Critical Section)を実行すると、次のスレッドのミューテックスのロックを解除し、自身のミューテックスの再取得を試みます。
- 初期化時には、メイン・スレッドのミューテックスを除き、すべてのミューテックスがロックされています。
' User thread (#N) algorithm (same principle for the main thread):
'
' Do
' |
' | Mutexlock(own thread mutex (#N))
' | |
' | Critical section of code
' | |
' | Mutexunlock(next thread mutex (#N+1))
' | |
' | Sleep tempo, 1
' |
' Loop Until quit = 1
Type UDT
Dim As Integer number
Dim As Integer tempo
Dim As Any Ptr pThread
Dim As ULongInt count
Static As Any Ptr pMutex(Any)
Static As Integer numberMax
Static As Integer quit
End Type
Dim As Any Ptr UDT.pMutex(Any)
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;
Locate .number, 30 + .number, 0
End With
End Sub
Sub Thread (ByVal p As Any Ptr)
Dim As Integer quit
Dim As UDT Ptr pUDT = p
With *pUDT
Do
MutexLock(.pMutex(.number))
Counter(pUDT)
quit = .quit
MutexUnlock(.pMutex((.number + 1) Mod (UDT.numberMax + 1)))
Sleep .tempo, 1
Loop Until quit = 1
End With
End Sub
UDT.numberMax = 6
ReDim UDT.pMutex(UDT.numberMax)
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)
UDT.pMutex(I) = MutexCreate
MutexLock(UDT.pMutex(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
Dim As String s
Do
s = Inkey
If s <> "" Then
UDT.quit = 1
End If
MutexUnlock(UDT.pMutex((u(0).number + 1) Mod (UDT.numberMax + 1)))
MutexLock(UDT.pMutex(u(0).number))
Sleep u(0).tempo, 1
Loop Until s <> ""
For I As Integer = 1 To UDT.numberMax
ThreadWait(u(I).pThread)
Next I
t = Timer - t
For I As Integer = 0 To UDT.numberMax
MutexDestroy(UDT.pMutex(I))
Next I
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
出力例:
' 102
' 102
' 102
' 102
' 102
' 102
'
' 51 increments per second
同期カウント(テンポ値が増加しても)のため同じ結果値になります。しかし、上記の同期方法(条件変数を使用)と比較して、実行時間のパフォーマンスが低下します。
参照