The tasks are placed on the TQueue by main( ) and are taken off the TQueue by the LiftOffRunner. Notice that LiftOffRunner can ignore the synchronization issues because they are solved by the TQueue.
Proper toasting
To solve the ToastOMatic.cpp problem, we can run the toast through TQueues between processes. And to do this, we will need actual toast objects, which maintain and display their state:
//: C11:ToastOMaticMarkII.cpp
// Solving the problems using TQueues
//{L} ZThread
#include "zthread/Thread.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
#include "zthread/Condition.h"
#include "zthread/ThreadedExecutor.h"
#include "TQueue.h"
#include
#include
#include
#include
using namespace ZThread;
using namespace std;
class Toast {
enum Status { dry, buttered, jammed };
Status status;
int id;
public:
Toast(int idn) : id(idn), status(dry) {}
void butter() { status = buttered; }
void jam() { status = jammed; }
string getStatus() const {
switch(status) {
case dry: return "dry";
case buttered: return "buttered";
case jammed: return "jammed";
default: return "error";
}
}
int getId() { return id; }
friend ostream& operator<<(ostream& os, const Toast& t) {
return os << "Toast " << t.id << ": " << t.getStatus();
}
};
typedef CountedPtr< TQueue
class Toaster : public Runnable {
ToastQueue toastQueue;
int count;
public:
Toaster(ToastQueue& tq) : toastQueue(tq), count(0) {
srand(time(0)); // Seed the random number generator
}
void run() {
try {
while(!Thread::interrupted()) {
int delay = rand()/(RAND_MAX/5)*100;
Thread::sleep(delay);
// Make toast
Toast t(count++);
cout << t << endl;
// Insert into queue
toastQueue->put(t);
}
} catch(Interrupted_Exception&) { /* Exit */ }
cout << "Toaster off" << endl;
}
};
// Apply butter to toast:
class Butterer : public Runnable {
ToastQueue dryQueue, butteredQueue;
public:
Butterer(ToastQueue& dry, ToastQueue& buttered)
: dryQueue(dry), butteredQueue(buttered) {}
void run() {
try {
while(!Thread::interrupted()) {
// Blocks until next piece of toast is available:
Toast t = dryQueue->get();
t.butter();
cout << t << endl;
butteredQueue->put(t);
}
} catch(Interrupted_Exception&) { /* Exit */ }
cout << "Butterer off" << endl;
}
};
// Apply jam to buttered toast:
class Jammer : public Runnable {
ToastQueue butteredQueue, finishedQueue;
public:
Jammer(ToastQueue& buttered, ToastQueue& finished)
: butteredQueue(buttered), finishedQueue(finished) {}
void run() {
try {
while(!Thread::interrupted()) {
// Blocks until next piece of toast is available:
Toast t = butteredQueue->get();
t.jam();
cout << t << endl;
finishedQueue->put(t);
}
} catch(Interrupted_Exception&) { /* Exit */ }
cout << "Jammer off" << endl;
}
};
// Consume the toast:
class Eater : public Runnable {
ToastQueue finishedQueue;
int counter;
public:
Eater(ToastQueue& finished)
: finishedQueue(finished), counter(0) {}
void run() {
try {
while(!Thread::interrupted()) {
// Blocks until next piece of toast is available:
Toast t = finishedQueue->get();
// Verify that the toast is coming in order,
// and that all pieces are getting jammed:
if(t.getId() != counter++ ||
t.getStatus() != "jammed") {
cout << ">>>> Error: " << t << endl;
exit(1);
} else
cout << "Chomp! " << t << endl;
}
} catch(Interrupted_Exception&) { /* Exit */ }
cout << "Eater off" << endl;
}
};
int main() {
try {
ToastQueue dryQueue(new TQueue
butteredQueue(new TQueue
finishedQueue(new TQueue
cout << "Press
ThreadedExecutor executor;
executor.execute(new Toaster(dryQueue));
executor.execute(new Butterer(dryQueue,butteredQueue));
executor.execute(
new Jammer(butteredQueue, finishedQueue));
executor.execute(new Eater(finishedQueue));
cin.get();
executor.interrupt();
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
Two things are immediately apparent in this solution: first, the amount and complexity of code within each Runnable class is dramatically reduced by the use of the TQueue, because the guarding, communication, and wait( )/signal( ) operations are now taken care of by the TQueue. The Runnable classes don’t have Mutexes or Condition objects anymore. Second, the coupling between the classes is eliminated because each class communicates only with its TQueues. Notice that the definition order of the classes is now independent. Less code and less coupling is always a good thing, which suggests that the use of the TQueue has a positive effect here, as it does on most problems.
Broadcast
The signal( ) function wakes up a single thread that is waiting on a Condition object. However, multiple threads may be waiting on the same condition object, and in that case you’ll want to wake them all up using broadcast( ) instead of signal( ).