still true. } // The condition we are waiting for is true! // We are still holding the lock, either from before entering the monitor or from // the last execution of "wait". // Critical section of code goes here, which has a precondition that our predicate // must be true. // This code might make cv's condition false, and/or make other condition variables' // predicates true. // Call signal or broadcast, depending on which condition variables' // predicates (who share mutex m) have been made true or may have been made true, // and the monitor semantic type being used. for (cv_x in cvs_to_signal) { signal(cv_x); // Or: broadcast(cv_x); } // One or more threads have been woken up but will block as soon as they try // to acquire m. // Release the mutex so that notified thread(s) and others can enter their critical // sections. release(m); Solving the bounded producer/consumer problem Having introduced the usage of condition variables, let us use it to revisit and solve the classic bounded producer/consumer problem. The classic solution is to use two monitors, comprising two condition variables sharing one lock on the queue: global volatile RingBuffer queue; // A thread-unsafe ring-buffer of tasks. global Lock queueLock; // A mutex for the ring-buffer of tasks. (Not a spin-lock.) global CV queueEmptyCV; // A condition variable for consumer threads waiting for the queue to // become non-empty. Its associated lock is "queueLock". global CV queueFullCV; // A condition variable for producer threads waiting for the queue to // become non-full. Its associated lock is also "queueLock". // Method representing each producer thread's behavior: public method producer() { while (true) { // Producer makes some new task to be added. task myTask = ...; // Acquire "queueLock" for the initial predicate check. queueLock.acquire(); // Critical section that checks if the queue is non-full. while (queue.isFull()) { // Release "queueLock", enqueue this thread onto "queueFullCV" and sleep this thread. wait(queueLock, queueFullCV); // When this thread is awoken, re-acquire "queueLock" for the next predicate check. } // Critical section that adds the task to the queue (note that we are holding "queueLock"). queue.enqueue(myTask); // Wake up one or all consumer threads that are waiting for the queue to be non-empty // now that it is guaranteed, so that a consumer thread will take the task. signal(queueEmptyCV); // Or: broadcast(queueEmptyCV); // End of critical sections. // Release