Conditional Variables
The built-in procedures that create, wait-for/signal, and destroy Conditional Variables.
Preamble:
A condition variable is a mechanism that allows threads to wait (without wasting CPU cycles) for some even to occur.
Several threads may wait on a condition variable, until some other thread signals this condition variable (thus sending a notification).
At this time, one of the threads waiting on this condition variable wakes up, and can act on the event. It is possible to also wake up all threads waiting on this condition variable by using a broadcast method on this variable.
A condition variable does not provide locking. Thus, a mutex must be used along with the condition variable, to provide the necessary locking when accessing this condition variable.
Conditional variable capability (and also mutex capability) can be fully used even with a detached thread (only its handler is no longer accessible by its identifier).
Several threads may wait on a condition variable, until some other thread signals this condition variable (thus sending a notification).
At this time, one of the threads waiting on this condition variable wakes up, and can act on the event. It is possible to also wake up all threads waiting on this condition variable by using a broadcast method on this variable.
A condition variable does not provide locking. Thus, a mutex must be used along with the condition variable, to provide the necessary locking when accessing this condition variable.
Conditional variable capability (and also mutex capability) can be fully used even with a detached thread (only its handler is no longer accessible by its identifier).
Creating / Destructing a conditional variable
CondCreate creates a condition variable, returning a handle identifier which is to be referred to when destroying the condition variable.
Condition variables created with CondCreate should be destroyed when no longer needed or before the end of the program with CondDestroy (to avoid leaking resources in the OS).
Create
Destroy
Description
Condition variables created with CondCreate should be destroyed when no longer needed or before the end of the program with CondDestroy (to avoid leaking resources in the OS).
Create
Destroy
Description
CondDestroy destroys a condition variable, freeing the resources it might hold.
No threads must be waiting on the condition variable on entrance to CondDestroy.
No threads must be waiting on the condition variable on entrance to CondDestroy.
Waiting-for/Signaling a conditional variable
The condition variable mechanism allows threads to suspend execution and relinquish the processor until some condition is true.
CondWait stops execution of the current thread until some condition becomes true.
CondSignal allows to restart one thread waiting on the conditional, while CondBroadCast allows to restart all threads waiting on the conditional.
Wait-for
ExampleCondWait stops execution of the current thread until some condition becomes true.
CondSignal allows to restart one thread waiting on the conditional, while CondBroadCast allows to restart all threads waiting on the conditional.
Wait-for
- Syntax:
- Usage:
Signal- Usage:
CondWait ( conditionalid, mutexid )
- Parameters:
conditionalid
The handle identifier of a conditional variable.
mutexid
The handle identifier of the mutex associated with this conditional variable, which must be locked when testing the condition and calling CondWait.
- Syntax:
- Parameter:
Description
or
- Usage:- Parameter:
Once the conditional variable is created with CondCreate and the threads are started, one of more of them (including the implicit main thread executing main program) can be set in waiting for the conditional state by CondWait.
They will be stopped until another thread signals by CondSignal that one among the waiting threads can restart.
CondBroadCast can be used to restart all threads waiting for the conditional.
A condition variable must always be associated with a mutex to avoid a race condition created by one thread preparing to wait and another thread which may signal the condition before the first thread actually waits on it resulting in a deadlock. The thread will be perpetually waiting for a signal that is never sent. Any mutex can be used, there is no explicit link between the mutex and the condition variable.
When calling CondWait, mutex should already be locked (using the same mutex as one used with CondSignal or CondBroadCast).
The detailed sequence is the following:
They will be stopped until another thread signals by CondSignal that one among the waiting threads can restart.
CondBroadCast can be used to restart all threads waiting for the conditional.
A condition variable must always be associated with a mutex to avoid a race condition created by one thread preparing to wait and another thread which may signal the condition before the first thread actually waits on it resulting in a deadlock. The thread will be perpetually waiting for a signal that is never sent. Any mutex can be used, there is no explicit link between the mutex and the condition variable.
When calling CondWait, mutex should already be locked (using the same mutex as one used with CondSignal or CondBroadCast).
The detailed sequence is the following:
- An atomic unlock of the mutex is applied before entering in waiting on the conditional variable in order to release other eventual threads using this mutex (this is why CondWait takes as arguments both the mutex and condition variable).
- The thread execution is suspended and does not consume any CPU time until the condition variable is signaled.
- When the condition variable becomes signaled, mutex is atomically re-locked.
- The thread execution can resume after the CondWait statement, but is suspended because of the locked mutex owned by the signal-caller.
- So, the signal-caller is then responsible for unlocking mutex in order that the called-thread completes the CondWait subroutine and that execution after the CondWait call can then really resume.
CondSignal restarts one thread waiting. It should be called after mutex is locked (using the same mutex as one used with CondWait):- The thread execution is suspended and does not consume any CPU time until the condition variable is signaled.
- When the condition variable becomes signaled, mutex is atomically re-locked.
- The thread execution can resume after the CondWait statement, but is suspended because of the locked mutex owned by the signal-caller.
- So, the signal-caller is then responsible for unlocking mutex in order that the called-thread completes the CondWait subroutine and that execution after the CondWait call can then really resume.
- If no threads are waiting on the conditional, nothing happens (the signal is lost forever).
- if several are waiting, only one is restarted:
When using CondBroadCast, this does not mean all threads are running together:- if several are waiting, only one is restarted:
. It might be that a condition variable that has several threads waiting on it is signaled many times, and yet one of the threads waiting on it never awakened.
. This is because it is not known which of the waiting threads is awakened when the variable is signaled.
. It might be that the awakened thread quickly comes back to waiting on the condition variables, and gets awakened again when the variable is signaled again, and so on (no wake-up priority based on history is assured).
. It is up to the programmer to make sure this situation does not occur if it implies bad behavior.
. This is because it is not known which of the waiting threads is awakened when the variable is signaled.
. It might be that the awakened thread quickly comes back to waiting on the condition variables, and gets awakened again when the variable is signaled again, and so on (no wake-up priority based on history is assured).
. It is up to the programmer to make sure this situation does not occur if it implies bad behavior.
- Each of them tries to lock the mutex again before returning from their wait function.
- And thus they will start running one by one, each one locking the mutex, doing their work, and freeing the mutex before the next thread gets its chance to run.
Note:- And thus they will start running one by one, each one locking the mutex, doing their work, and freeing the mutex before the next thread gets its chance to run.
- It is a good habit to use CondWait in a protected way against eventual spurious wake-ups.
- For that, CondWait is put within a loop for checking that a Boolean predicate is indeed true (predicate set true by another thread just before executing CondSignal or CondBroadCast) when the thread has finished waiting:
- The loop can terminate only when the predicate is true.
- On the other hand, if the predicate is already true before the thread reaches the loop, CondWait is downright skipped (allowing to take into account a case of CondSignal or CondBroadCast that would have been lost otherwise, because prematurely executed in a second thread before the first thread is really waiting for this).
Pseudo-codes for detailed coding by applying all proper above rules:- For that, CondWait is put within a loop for checking that a Boolean predicate is indeed true (predicate set true by another thread just before executing CondSignal or CondBroadCast) when the thread has finished waiting:
signal-caller:
predicate = True
Condsignal(conditionalid)
waiting-called:Condsignal(conditionalid)
While predicate <> True
predicate = False
Condwait(conditionalid, mutexid)
Wendpredicate = False
- On the other hand, if the predicate is already true before the thread reaches the loop, CondWait is downright skipped (allowing to take into account a case of CondSignal or CondBroadCast that would have been lost otherwise, because prematurely executed in a second thread before the first thread is really waiting for this).
- Pseudo-code sub-section for a thread:
- Pseudo-code section for a thread:
- Pseudo-code section example for each of 2 threads:
- Pseudo-code for the "waiting-for" critical sub-section (with the links to/from critical section items of other thread):
- Pseudo-code for the "signaling" critical sub-section (with the links to/from the critical section items of other thread):
' Principle of mutual exclusion + waiting-for, 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 + signaling, 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) )
- Pseudo-code for the "signaling, then waiting-for" critical section (with the links to/from the critical section items of other thread):
- Pseudo-code for the "waiting-for, then signaling" critical section (with the links to/from critical section items of other thread):
' Principle (1) of mutual exclusion + mutual synchronization, for a thread 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) ' Do_something_1_with_exclusion ' booleanOT = True ---------------------------> to While booleanOT <> True ' CONDSIGNAL(conditionalID) ------------------> to CONDWAIT(conditionalID, 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 ' Do_something_2_with_exclusion ' MUTEXUNLOCK(mutexID) -----------------------> to MUTEXLOCK(mutexID) or ( atomic_mutex_re-lock(mutexID) )
' Principle (2) of mutual exclusion + mutual synchronization, for a thread 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) ' Do_something_1_with_exclusion ' 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 ' Do_something_2_with_exclusion ' booleanOT = True ----------------------------> to While booleanOT <> True ' CONDSIGNAL(conditionalID) -------------------> to CONDWAIT(conditionalID, mutexID) ' MUTEXUNLOCK(mutexID) ------------------------> to MUTEXLOCK(mutexID) or ( atomic_mutex_re-lock(mutexID) )
- Pseudo-code for the "signaling, then waiting-for" critical section of a main thread and for "waiting-for, then signaling" critical section of a child thread (with the links to/from critical section items of both threads):
' Principle (example) of mutual exclusion + mutual synchronization sections, between 2 threads, ' using the principle (1) for the main thread, and the principle (2) for the child thread ' (connecting lines join the sender(s) and receiver(s) impacted by each action occurring during the sequence) ' ' Main Thread Child Thread ' MUTEXLOCK(mutexID) <-----------------------------. .-------------.-------------> MUTEXLOCK(mutexID) ' Do_something_1_with_exclusion | | | Do_something_1_with_exclusion ' boolC = True ----------------------------------- | --- | ----------- | ------------> While boolC <> True ' CONDSIGNAL(conditionalID) ---------------------- | --- | ----------- | ---. .-------- ( atomic_mutex_unlock(mutexID) ) ' While boolM <> True <--------------------------- | --- | ---------. | '--- | ------> CONDWAIT(conditionalID, mutexID) ' ( atomic_mutex_unlock(mutexID) ) ------. | | | '-------- | ------> ( atomic_mutex_re-lock(mutexID) ) ' CONDWAIT(conditionalID, mutexID) <---- | --- | --- | ------. | | Wend ' ( atomic_mutex_re-lock(mutexID) ) <--- | ----'---- | ---. | | | boolC = False ' Wend | | | | | | Do_something_2_with_exclusion ' boolM = False | | | | '----------- | --- boolM = True ' Do_something_2_with_exclusion | | | '-------------- | --- CONDSIGNAL(conditionalID) ' MUTEXUNLOCK(mutexID) ----------------------'-----------' '------------------'---- MUTEXUNLOCK(mutexID)
The second example on the previous page (Mutual Exclusion) is modified by using a conditional variable to exhibit a synchronization example.
So, the two sections of code, previously protected by a mutual exclusion block [Mutexlock ... Mutexunlock], are now synchronized in order that each thread displays one after the other its character sequence ("[M]" or "(C)").
In this example, the two threads critical sections have their sub-sections in an reverse order:
See alsoSo, the two sections of code, previously protected by a mutual exclusion block [Mutexlock ... Mutexunlock], are now synchronized in order that each thread displays one after the other its character sequence ("[M]" or "(C)").
In this example, the two threads critical sections have their sub-sections in an reverse order:
- main thread critical section: at first signaling, then waiting for,
- child thread critical section: at first waiting for, then signaling.
Therefore the main thread will always be the first to display its sequence:- child thread critical section: at first waiting for, then signaling.
' Principle of mutual exclusion + mutual synchronization between 2 threads with loop ' (connecting lines join the sender(s) and receiver(s) impacted by each action occurring during the sequence) ' ' Main Thread Child Thread ' ..... ..... ' Loop Loop ' MUTEXLOCK(mutID) <-----------------------------. .-------------.---------> MUTEXLOCK(mutID) ' Do_something_with_exclusion .---- | --- | ----------- | --------> While boolC <> True ' boolC = True ----------------------------' | | | .-------- ( atomic_mutex_unlock(mutID) ) ' CONDSIGNAL(condID) --------------------------- | --- | ----------- | --- | ------> CONDWAIT(condID, mutID) ' While boolM <> True <------------------------- | --- | ---------. '---- | ------> ( atomic_mutex_re-lock(mutID) ) ' ( atomic_mutex_unlock(mutID) ) ------. | | | | Wend ' CONDWAIT(condID, mutID) <----------- | --- | --- | ------. | | boolC = False ' ( atomic_mutex_re-lock(mutID) ) <--- | ----'---- | ---. | | | Do_something_with_exclusion ' Wend | | | | '------- | --- boolM = True ' boolM = False | | | '---------- | --- CONDSIGNAL(condID) ' MUTEXUNLOCK(mutID) ----------------------'-----------' '--------------'---- MUTEXUNLOCK(mutID) ' End Loop End Loop ' ..... .....
Declare Sub thread (ByVal userdata As Any Ptr)
Dim As Any Ptr threadID '' declaration of an 'Any Ptr' thread-ID of the child thread
Dim Shared As Any Ptr mutID '' declaration of a global 'Any Ptr' mutex-ID
mutID = MutexCreate '' creation of the mutex
Dim Shared As Boolean boolM, boolC '' declaration of 2 global 'Boolean' boolM and boolC as predicates
Dim Shared As Any Ptr condID '' declaration of a global 'Any Ptr' conditional-ID
condID = CondCreate '' creation of the conditional
Print """[M]"": from 'Main' thread"
Print """(C)"": from 'Child' thread"
Print
threadID = ThreadCreate(@thread) '' creation of the child thread from the main thread
For I As Integer = 1 To 10 '' 'For' loop of the main thread
MutexLock(mutID) '' set mutex locked at the beginning of the exclusive section
Print "[";
Sleep 50, 1
Print "M";
Sleep 50, 1
Print "]";
boolC = True '' set to 'True' the predicate for the child thread
CondSignal(condID) '' signal to the child thread
While boolM <> True '' test predicate from the child thread
CondWait(condID, mutID) '' wait for signal from the child thread
Wend
boolM = False '' reset the predicate from the child thread
MutexUnlock(mutID) '' set mutex unlocked at the end of the exclusive section
Sleep 50, 1
Next I
ThreadWait(threadID) '' waiting for the child thread termination
Print
Print "'Child' thread finished"
MutexDestroy(mutID) '' destruction of the mutex
CondDestroy(condID) '' destruction of the conditional
Sleep
Sub thread (ByVal userdata As Any Ptr) '' sub executed by the child thread
For I As Integer = 1 To 10 '' 'For' loop of the child thread
MutexLock(mutID) '' set mutex locked at the beginning of the exclusive section
While boolC <> true '' test predicate from the main thread
CondWait(condID, mutID) '' wait for signal from the main thread
Wend
boolC = False '' reset the predicate from the main thread
Print "(";
Sleep 50, 1
Print "C";
Sleep 50, 1
Print ")";
boolM = True '' set to 'True' the predicate for the main thread
CondSignal(condID) '' signal to the child thread
MutexUnlock(mutID) '' set mutex unlocked at the end of the exclusive section
Sleep 250, 1
Next I
End Sub
Output:Dim As Any Ptr threadID '' declaration of an 'Any Ptr' thread-ID of the child thread
Dim Shared As Any Ptr mutID '' declaration of a global 'Any Ptr' mutex-ID
mutID = MutexCreate '' creation of the mutex
Dim Shared As Boolean boolM, boolC '' declaration of 2 global 'Boolean' boolM and boolC as predicates
Dim Shared As Any Ptr condID '' declaration of a global 'Any Ptr' conditional-ID
condID = CondCreate '' creation of the conditional
Print """[M]"": from 'Main' thread"
Print """(C)"": from 'Child' thread"
threadID = ThreadCreate(@thread) '' creation of the child thread from the main thread
For I As Integer = 1 To 10 '' 'For' loop of the main thread
MutexLock(mutID) '' set mutex locked at the beginning of the exclusive section
Print "[";
Sleep 50, 1
Print "M";
Sleep 50, 1
Print "]";
boolC = True '' set to 'True' the predicate for the child thread
CondSignal(condID) '' signal to the child thread
While boolM <> True '' test predicate from the child thread
CondWait(condID, mutID) '' wait for signal from the child thread
Wend
boolM = False '' reset the predicate from the child thread
MutexUnlock(mutID) '' set mutex unlocked at the end of the exclusive section
Sleep 50, 1
Next I
ThreadWait(threadID) '' waiting for the child thread termination
Print "'Child' thread finished"
MutexDestroy(mutID) '' destruction of the mutex
CondDestroy(condID) '' destruction of the conditional
Sleep
Sub thread (ByVal userdata As Any Ptr) '' sub executed by the child thread
For I As Integer = 1 To 10 '' 'For' loop of the child thread
MutexLock(mutID) '' set mutex locked at the beginning of the exclusive section
While boolC <> true '' test predicate from the main thread
CondWait(condID, mutID) '' wait for signal from the main thread
Wend
boolC = False '' reset the predicate from the main thread
Print "(";
Sleep 50, 1
Print "C";
Sleep 50, 1
Print ")";
boolM = True '' set to 'True' the predicate for the main thread
CondSignal(condID) '' signal to the child thread
MutexUnlock(mutID) '' set mutex unlocked at the end of the exclusive section
Sleep 250, 1
Next I
End Sub
"[M]": from 'Main' thread "(C)": from 'Child' thread [M](C)[M](C)[M](C)[M](C)[M](C)[M](C)[M](C)[M](C)[M](C)[M](C) 'Child' thread finished
- CondCreate, CondDestroy,
- CondWait, CondSignal, CondBroadCast
- Multi-Threading Overview
- Threads
- Mutual Exclusion
- Critical Sections
- Critical Sections FAQ
Back to Programmer's Guide