Condition variables are similar to monitors in that they allow a thread to enter a critical region, test a condition, and sleep inside the critical region while releasing its lock. Another thread then is free to enter the critical region, change the condition, and eventually signal the sleeping thread, which resumes at the point where it went to sleep and with the critical region once again locked.
Note that condition variables provide a subset of the functionality of monitors, so a monitor can always be used instead of a condition variable. However, condition variables are smaller, which may be important if you are seriously constrained with respect to memory.
Condition variables are provided by the
IceUtil::Cond class. Here is its interface:
Using a condition variable is very similar to using a monitor. The main difference in the
Cond interface is that the
timedWait member functions are template functions, instead of the entire class being a template. The member functions behave as follows:
This function suspends the calling thread and, at the same time, releases the lock of the condition variable. A thread suspended inside a call to
waitcan be woken up by another thread that calls
waitcompletes, the suspended thread resumes execution with the lock held.
This function suspends the calling thread for up to the specified timeout. If another thread calls
broadcastand wakes up the suspended thread before the timeout expires, the call returns true and the suspended thread resumes execution with the lock held. Otherwise, if the timeout expires, the function returns false. Wait intervals are represented by instances of the
This function wakes up a single thread that is currently suspended in a call to
timedWait. If no thread is suspended in a call to
timedWaitat the time
signalis called, the signal is lost (that is, calls to
signalare not remembered if there is no thread to be woken up). Note that signalling does not necessarily run another thread immediately; the thread calling
signalmay continue to run. However, depending on the underlying thread library,
signalmay also cause an immediate context switch to another thread.
This function wakes up all threads that are currently suspended in a call to
timedWait. As for
signal, calls to
broadcastare lost if no threads are suspended at the time.
You must adhere to a few rules for condition variables to work correctly:
- Do not call
timedWaitunless you hold the lock.
- When returning from a
waitcall, you must re-test the condition before proceeding, just as for a monitor.
In contrast to monitors, which require you to call
notifyAll with the lock held, condition variables permit you to call
broadcast without holding the lock. Here is a code example that changes a condition and signals on a condition variable:
This code is correct and will work as intended, but it is potentially inefficient. Consider the code executed by the waiting thread:
Again, this code is correct and will work as intended. However, consider what can happen once the first thread calls
signal. It is possible that the call to
signal will cause an immediate context switch to the waiting thread. But, even if the thread implementation does not cause such an immediate context switch, it is possible for the signalling thread to be suspended after it has called
signal, but before it unlocks the mutex
m. If this happens, the following sequence of events occurs:
- The waiting thread is still suspended inside the implementation of
waitand is now woken up by the call to
- The now-awake thread tries to acquire the mutex
mbut, because the signalling thread has not yet released the mutex, is suspended again waiting for the mutex to be unlocked.
- The signalling thread is scheduled again and leaves the scope enclosing
sync, which unlocks the mutex, making the thread waiting for the mutex runnable.
- The thread waiting for the mutex acquires the mutex and retests its condition.
While the preceding scenario is functionally correct, it is inefficient because it incurs two extra context switches between the signalling thread and the waiting thread. Because context switches are expensive, this can have quite a large impact on run-time performance, especially if the critical region is small and the condition changes frequently.
You can avoid the inefficiency by unlocking the mutex before calling signal:
By arranging the code as shown, you avoid the additional context switches because, when the waiting thread is woken up by the call to
signal, it succeeds in acquiring the mutex before returning from
wait without being suspended and woken up again first.
As for monitors, you should exercise caution in using
broadcast, particularly if you have many threads waiting on a condition. Condition variables suffer from the same potential problem as monitors with respect to
broadcast, namely, that all threads that are currently suspended inside
wait can immediately attempt to acquire the mutex, but only one of them can succeed and all other threads are suspended again. If your application is sensitive to this condition, you may want to consider waking threads in a more controlled manner.