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:

{zcode:cpp}
class Cond : private noncopyable {
public:

    Cond();
    ~Cond();

    void signal();
    void broadcast();

    template<typename Lock>
    void wait(const Lock& lock) const;

    template<typename Lock>
    bool timedWait(const Lock& lock, const Time& timeout) const;
};
{zcode}

Using a condition variable is very similar to using a monitor. The main difference in the Cond interface is that the wait and timedWait member functions are template functions, instead of the entire class being a template. The member functions behave as follows:

You must adhere to a few rules for condition variables to work correctly:

In contrast to monitors, which require you to call notify and notifyAll with the lock held, condition variables permit you to call signal and broadcast without holding the lock. Here is a code example that changes a condition and signals on a condition variable:

{zcode:cpp}
Mutex m;
Cond c;

// ...

{
     Mutex::Lock sync(m);

     // Change some condition other threads may be sleeping on...

     c.signal();

     // ...
} // m is unlocked here
{zcode}

This code is correct and will work as intended, but it is potentially inefficient. Consider the code executed by the waiting thread:

{zcode:cpp}
{
    Mutex::Lock sync(m);

    while(!condition) {
        c.wait(sync);
    }

    // Condition is now true, do some processing...

} // m is unlocked here
{zcode}

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:

  1. The waiting thread is still suspended inside the implementation of wait and is now woken up by the call to signal.
  2. The now-awake thread tries to acquire the mutex m but, because the signalling thread has not yet released the mutex, is suspended again waiting for the mutex to be unlocked.
  3. The signalling thread is scheduled again and leaves the scope enclosing sync, which unlocks the mutex, making the thread waiting for the mutex runnable.
  4. 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:

{zcode:cpp}
Mutex m;
Cond c;

// ...

{
     Mutex::Lock sync(m);

     // Change some condition other threads may be sleeping on...

} // m is unlocked here

c.signal(); // Signal with the lock available
{zcode}

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.

See Also