Condition Variables

Basic Concept

If monitor can only provide a centralized form of mutex locks that mimics a mini-OS, it will not be very useful. Consider an operating system. An I/O request from a user program via a system call may not be completed immediately (e.g., waiting for an I/O operation to complete). Should this happen, the operating system may postpone this I/O service and serve another user until the I/O operation completes. Then, the operating system resumes the I/O service. In this case, the user "feels" that he is blocked within the operating system.

For a similar reason, a thread in a monitor may have to block itself because of its request may not complete immediately. This waiting for an event (e.g., I/O completion) to occur is realized by condition variables.

A condition variable indicates an event and has no value. More precisely, one cannot store a value into nor retrieve a value from a condition variable. If a thread must wait for an event to occur, that thread waits on the corresponding condition variable. If another thread causes an event to occur, that thread simply signals the corresponding condition variable. Thus, a condition variable has a queue for those threads that are waiting the corresponding event to occur to wait on, and, as a result, the original monitor is extended to the following. The private data section now can have a number of condition variables, each of which has a queue for threads to wait on.

Condition Variable Operations: Wait and Signal

There are only two operations that can be applied to a condition variable: wait and signal. When a thread executes a wait call (in a monitor, of course) on a condition variable, it is immediately suspended and put into the waiting queue of that condition variable. Thus, this thread is suspended and is waiting for the event that is represented by the condition variable to occur. Because the calling thread is the only thread that is running in the monitor, it "owns" the monitor lock. When it is put into the waiting queue of a condition variable, the system will automatically take the monitor lock back. As a result, the monitor becomes empty and another thread can enter.

Eventually, a thread will cause the event to occur. To indicate a particular event occurs, a thread calls the signal method on the corresponding condition variable. At this point, we have two cases to consider. First, if there are threads waiting on the signaled condition variable, the monitor will allow one of the waiting threads to resume its execution and give this thread the monitor lock back. Second, if there is no waiting thread on the signaled condition variable, this signal is lost as if it never occurs.

Therefore, wait and signal for a condition variable is very similar to the notification technique of a semaphore: One thread waits on an event and resumes its execution when another thread causes the event to occur. However, there are major differences as will be discussed on a later page.

Condition Variables in ThreadMentor

Under ThreadMentor, you can declare a condition variable as a class of type Condition. This class has two methods Wait() and Signal(). Note that class Condition can only be used in a monitor class and is not available outside of a monitor.

Let us use a simple problem to illustrate the use of condition variables. We want to design a monitor with two methods Rest() and Knock(). Threads that call Rest() will be blocked (i.e., taking a rest) until a specific number of knocks has occurred. Each knock is generated by a call to method Knock(). The number of knocks that must occur before a resting thread can continue is specified by the monitor constructor. For simplicity, we assume that there is only one thread that keeps calling Rest() and that all other threads repeatedly call Knock().

Because threads may be blocked in a monitor, we need a condition variable Event. Thus, the thread that calls Rest() is blocked on Event, and all other threads that call Knock() will signal condition variable Event, if necessary. In the following monitor definition, private variable Max_Count is the number knocks that are required to wake up the resting thread, variable counter counts the number of knocks received, and variable HasWaiting indicates if there is a resting thread waiting on condition variable Event.

class MyMonitor : public Monitor
{
     public:
          MyMonitor(int count);
          void   Rest(void);
          void   Knock(void);
     private:
          Condition  Event;
          int        Max_Count;
          int        counter;
          int        HasWaiting;
};

void  MyMonitor::Rest(void)
{
     MonitorBegin();
          HasWaiting = 1;     // I am waiting
          Event.Wait();
          HasWaiting = 0;     // no one is waiting
     MonitorEnd();
}

void  MyMonitor::Knock(void)
{
     MonitorBegin();
          if (HasWaiting) {
               counter++;
               if (counter == Max_Count) {
                    Event.Signal();
                    counter = 0;
               }
          }
     MonitorEnd();
}

Method Rest() is quite easy. It sets HasWaiting to 1, indicating the resting thread is waiting, and immediately waits on the condition variable Event. At this point, the system takes the monitor lock from the reseting thread for the knocking threads to enter.

Method Knock() is a little more complicated. Because knocking counts only if the there is a resting thread waiting on the condition variable Event, Knock() checks if the resting thread is there. If there is no resting thread waiting, Knock() does nothing; otherwise, the knocking count is increased by 1. If this is a full count, the condition variable Event is signaled to release the resting thread, and then resets the counter counter and variable HasWaiting. When the resting thread resumes, it will be given back the monitor lock.