In waitForWaxing( ), the waxOn flag is checked, and if it is false, the calling thread is suspended by calling wait( ) on the Condition object. It’s important that this occur inside a guarded clause, in which the thread has acquired the lock (here, by creating a Guard object). When you call wait( ), the thread is suspended and
In order for a thread to wake up from a wait( ), it must first reacquire the mutex that it released when it entered the wait( ). The thread will not wake up until that mutex becomes available.
The call to wait( ) is placed inside a while loop that checks the condition of interest. This is important for two reasons:
· It is possible that when the thread gets a signal( ), some other condition has changed that is not associated with the reason that we called wait( ) here. If that is the case, this thread should be suspended again until its condition of interest changes.
· By the time this thread awakens from its wait( ), it’s possible that some other task has changed things such that this thread is unable or uninterested in performing its operation at this time. Again, it should be re-suspended by calling wait( ) again.
Because these two reasons are always present when you are calling wait( ), always write your call to wait( ) inside a while loop that tests for your condition(s) of interest.
WaxOn::run( ) represents the first step in the process of waxing the car, so it performs its operation (a call to sleep( ) to simulate the time necessary for waxing). It then tells the car that waxing is complete, and calls waitForBuffing( ), which suspends this thread with a wait( ) until the WaxOff process calls buffed( ) for the car, changing the state and calling notify( ). WaxOff::run( ), on the other hand, immediately moves into waitForWaxing( ) and is thus suspended until the wax has been applied by WaxOn and waxed( ) is called. When you run this program, you can watch this two-step process repeat itself as control is handed back and forth between the two threads. When you press the
Producer-consumer relationships
A common situation in threading problems is the
To show this problem, consider a machine that has three tasks: one to make toast, one to butter the toast, and one to put jam on the buttered toast.
//: C11:ToastOMatic.cpp
// Problems with thread cooperation.
//{L} ZThread
#include "zthread/Thread.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
#include "zthread/Condition.h"
#include "zthread/ThreadedExecutor.h"
#include
#include
#include
using namespace ZThread;
using namespace std;
// Apply jam to buttered toast:
class Jammer : public Runnable {
Mutex lock;
Condition butteredToastReady;
bool gotButteredToast;
int jammed;
public:
Jammer(): butteredToastReady(lock) {
gotButteredToast = false;
jammed = 0;
}
void moreButteredToastReady() {
Guard
gotButteredToast = true;
butteredToastReady.signal();
}
void run() {
try {
while(!Thread::interrupted()) {
{
Guard
while(!gotButteredToast)
butteredToastReady.wait();
jammed++;
}
cout << "Putting jam on toast " << jammed << endl;
{
Guard
gotButteredToast = false;
}
}
} catch(Interrupted_Exception&) { /* Exit */ }
cout << "Jammer off" << endl;
}
};
// Apply butter to toast:
class Butterer : public Runnable {
Mutex lock;
Condition toastReady;
CountedPtr
bool gotToast;
int buttered;
public:
Butterer(CountedPtr
: jammer(j), toastReady(lock) {
gotToast = false;
buttered = 0;
}
void moreToastReady() {
Guard
gotToast = true;
toastReady.signal();
}