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 {RunByHand}
// Problems with thread cooperation.
//{L} ZThread
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "zthread/Thread.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
#include "zthread/Condition.h"
#include "zthread/ThreadedExecutor.h"
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<Mutex> g(lock);
gotButteredToast = true;
butteredToastReady.signal();
}
void run() {
try {
while(!Thread::interrupted()) {
{
Guard<Mutex> g(lock);
while(!gotButteredToast)
butteredToastReady.wait();
++jammed;
}
cout << "Putting jam on toast "
<< jammed << endl;
{
Guard<Mutex> g(lock);
gotButteredToast = false;
}
}
} catch(Interrupted_Exception&) { /* Exit */ }
cout << "Jammer off" << endl;
}
};
// Apply butter to toast:
class Butterer : public Runnable {
Mutex lock;
Condition toastReady;
CountedPtr<Jammer> jammer;
bool gotToast;
int buttered;
public:
Butterer(CountedPtr<Jammer>& j)
: toastReady(lock), jammer(j) {
gotToast = false;
buttered = 0;
}
void moreToastReady() {
Guard<Mutex> g(lock);
gotToast = true;
toastReady.signal();
}
void run() {
try {
while(!Thread::interrupted()) {
{
Guard<Mutex> g(lock);
while(!gotToast)
toastReady.wait();
++buttered;
}
cout << "Buttering toast "
<< buttered << endl;
jammer->moreButteredToastReady();
{
Guard<Mutex> g(lock);
gotToast = false;
}
}
} catch(Interrupted_Exception&) { /* Exit */ }
cout << "Butterer off" <<
endl;
}
};
class Toaster : public Runnable {
CountedPtr<Butterer> butterer;
int toasted;
public:
Toaster(CountedPtr<Butterer>& b) :
butterer(b) {
toasted = 0;
}
void run() {
try {
while(!Thread::interrupted()) {
Thread::sleep(rand()/(RAND_MAX/5)*100);
// ...
// Create new toast
// ...
cout << "New toast " <<
++toasted << endl;
butterer->moreToastReady();
}
} catch(Interrupted_Exception&) { /* Exit */ }
cout << "Toaster off" <<
endl;
}
};
int main() {
srand(time(0)); // Seed the random number generator
try {
cout << "Press <Return> to
quit" << endl;
CountedPtr<Jammer> jammer(new Jammer);
CountedPtr<Butterer> butterer(new
Butterer(jammer));
ThreadedExecutor executor;
executor.execute(new Toaster(butterer));
executor.execute(butterer);
executor.execute(jammer);
cin.get();
executor.interrupt();
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
The classes are defined in the reverse order that they
operate to simplify forward-referencing issues.
The difference between this example and the previous one is
that, at least conceptually, something is being produced here: toast. The rate
of toast production is randomized a bit, to add some uncertainty. And you ll
see that when you run the program, things aren t going right because many
pieces of toast appear to be getting dropped on the floor not buttered, not
jammed.