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).
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
- Syntax:
`Declare Function CondCreate ( ) As Any Ptr`
- Usage:
`conditionalid = CondCreate`
- Return value:
The `Any Ptr` handle `(conditionalid)` to the conditional variable created, or the null pointer (`0`) on failure.
Destroy
- Syntax:
`Declare Sub CondDestroy ( ByVal conditionalid As Any Ptr )`
- Usage:
`CondDestroy ( conditionalid )`
- Parameter:
`conditionalid`
The `Any Ptr` handle of the conditional variable to be destroyed.
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`.
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
- Syntax:
`Declare Sub CondWait ( ByVal conditionalid As Any Ptr, ByVal mutexid As Any Ptr )`
- 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`.
Signal
- Syntax:
`Declare Sub CondSignal ( ByVal conditionalid As Any Ptr )`
or
`Declare Sub CondBroadcast ( ByVal conditionalid As Any Ptr )`
- Usage:
`CondSignal ( conditionalid )`
or
`CondBroadcast ( conditionalid )`
- Parameter:
`conditionalid`
The `Any Ptr` handle of the conditional variable to be signaled.
Description
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:
- 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`):
- If no threads are waiting on the conditional, nothing happens (the signal is lost forever).
- 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.
When using `CondBroadcast`, this does not mean all threads are running together:
- 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:
- 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:
signal-caller:
`predicate = True
Condsignal(conditionalid)`
waiting-called:
`While predicate <> True
Condwait(conditionalid, mutexid)
Wend
predicate = False`
- 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:

- Pseudo-code sub-section for a thread:

- Pseudo-code for the "waiting-for" critical sub-section (with the links to/from 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) )

- Pseudo-code for the "signaling" critical sub-section (with the links to/from the critical section items of other thread):

    '  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 section for a thread:

- Pseudo-code for the "signaling, then waiting-for" critical section (with the links to/from the 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) )

- Pseudo-code for the "waiting-for, then signaling" critical section (with the links to/from critical section items of other thread):

    '  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 section example for each of 2 threads:

- 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)

Example
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:
- 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:

    '  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:

    "[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

See also

- `CondCreate`, `CondDestroy`,
- `CondWait`, `CondSignal`, `CondBroadcast`
- Multi-Threading Overview
- Threads
- Mutual Exclusion
- Critical Sections
- Critical Sections FAQ