Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Producer consumer relationships

A common situation in threading problems is the producer-consumer relationship, where one task is creating objects and other tasks are consuming them. In such a situation, make sure that (among other things) the consuming tasks do not accidentally skip any of the produced objects.

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.

Jammer and Butterer both contain a Mutex, a Condition, and some kind of internal state information that changes to indicate that the process should suspend or resume. (Toaster doesn t need these since it is the producer and doesn t have to wait on anything.) The two run( ) functions perform an operation, set a state flag, and then call wait( ) to suspend the task. The moreToastReady( ) and moreButteredToastReady( ) functions change their respective state flags to indicate that something has changed and the process should consider resuming and then call signal( ) to wake up the thread.

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.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire