Critical Sections
The proper use of the built-in procedures in Critical Sections for handling the concurrency with other threads.
Preamble:
A critical section is a part of a multi-threading program that has to be executed as atomic actions (no concurrence with other threads executing similar actions):
- It is a piece of a program that requires mutual exclusion of access.
- Typically, the critical section accesses a shared resource, such as a data structure, a peripheral device, or a network connection, that does not allow multiple concurrent accesses.
When a program starts up, one thread already begins running immediately. This is usually called the "main" thread of the program, because it is the one that is executed when a program begins:- Typically, the critical section accesses a shared resource, such as a data structure, a peripheral device, or a network connection, that does not allow multiple concurrent accesses.
- It is the thread from which user may spawn other "child" threads (which in turn may spawn other "sub-child" threads).
- Often, it must be the last thread to finish execution because it performs various shutdown actions (as a "child" thread must also do so with respect to its eventual "sub-child" threads spawned).
- But other than that, it can also compete (with its own critical sections) with all other threads explicitly spawned by user.
- Often, it must be the last thread to finish execution because it performs various shutdown actions (as a "child" thread must also do so with respect to its eventual "sub-child" threads spawned).
- But other than that, it can also compete (with its own critical sections) with all other threads explicitly spawned by user.
Basic algorithms
Using the built-in procedures provided by FreeBASIC, the method to ensure exclusive use of a critical section may be designed in a algorithm either asynchronous or synchronous, which applies to the threads.
- Basic algorithm for an asynchronous method using a mutex:
- Basic algorithm for a synchronous method using a condwait then a condsignal/condbroadcast (and mutex):
By getting the mutex locking, the thread can take the exclusive control to access the shared resource.
When shared resource access is ended, the thread unlocks the mutex.
Algorithm:
When shared resource access is ended, the thread unlocks the mutex.
Algorithm:
' Mutexlock ' | ' Critical section of code ' | ' Mutexunlock
The thread waits for a Boolean predicate is indeed true and also a condition signal (condwait) before executing its critical section.
When shared resource access is ended, the thread sets another Boolean predicate then sends a condition signal to other thread(s) (condsignal or condbroadcast).
Algorithm:
Similar algorithm with waiting-for, then signaling are put in reverse order:
When shared resource access is ended, the thread sets another Boolean predicate then sends a condition signal to other thread(s) (condsignal or condbroadcast).
Algorithm:
' Mutexlock ' | ' While my_predicate <> True ' | Condwait ' Wend ' my_predicate = False ' | ' Critical section of code ' | ' other_predicate = True ' Condsignal or Condbroadcast ' | ' Mutexunlock
' Mutexlock ' | ' Critical section of code ' | ' other_predicate = True ' Condsignal or Condbroadcast ' | ' While my_predicate <> True ' | Condwait ' Wend ' my_predicate = False ' | ' Mutexunlock
In the two following examples, the shared resource is the input/output display device:
A different tempo value is set at the end of each thread loop (from smaller to bigger).
A structure (UDT) groups all variables necessary to the threads. A pointer to each UDT instance is passed to each thread at its creation phase (with threadcreate).
- Print its counter for each of 6 user threads (and read the flag 'quit').
- Catching a key-press (any one) for the main thread (and if yes, set the flag 'quit' to 'true').
The outputting procedure ('Sub Counter()') has voluntarily a tempo between cursor positioning and printing, and also a repositioning of text cursor at middle of line before ending, in order to thoroughly check that there is no overlap between the critical sections executions (at opposite, one can see the result by removing some code dedicated to mutual exclusion processing).- Catching a key-press (any one) for the main thread (and if yes, set the flag 'quit' to 'true').
A different tempo value is set at the end of each thread loop (from smaller to bigger).
A structure (UDT) groups all variables necessary to the threads. A pointer to each UDT instance is passed to each thread at its creation phase (with threadcreate).
- Asynchronous method example using one mutex for all threads:
- Synchronous method example using a condwait then a condbroadcast (and one mutex) for all threads:
- Weird synchronous algorithm using a mutex for each thread, by self lock and mutual unlock:
' 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
Output example: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
' 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
Output example: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 ' ' 51 increments per second
- When one thread has run its critical section, it unlocks the mutex of the next thread and attempts to re-obtain its own mutex.
- At initialization all mutexes are locked, except the mutex of the main thread.
Same result values because synchronous counting (despite of increasing tempo values).
- At initialization all mutexes are locked, except the mutex of the main thread.
' 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
MutexUnlock(UDT.pMutex(u(0).number))
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(u(0).number))
s = Inkey
If s <> "" Then
UDT.quit = 1
End If
MutexUnlock(UDT.pMutex((u(0).number + 1) Mod (UDT.numberMax + 1)))
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
Output example: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
MutexUnlock(UDT.pMutex(u(0).number))
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(u(0).number))
s = Inkey
If s <> "" Then
UDT.quit = 1
End If
MutexUnlock(UDT.pMutex((u(0).number + 1) Mod (UDT.numberMax + 1)))
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
- Multi-Threading Overview
- Threads
- Mutual Exclusion
- Conditional Variables
- Critical Sections FAQ
- Emulate a TLS (Thread Local Storage) and a TP (Thread Pooling) feature
Back to Programmer's Guide