#include <iostream>
#include <sstream>
class Display { // Share one of these among all threads
ZThread::Mutex iolock;
public:
void output(std::ostringstream& os) {
ZThread::Guard<ZThread::Mutex> g(iolock);
std::cout << os.str();
}
};
#endif // DISPLAY_H ///:~
This way, all of the standard operator<<( ) functions are predefined for us and the object can be built in memory using familiar ostream operators. When a task wants to display output, it creates a temporary ostringstream object that it uses to build up the desired output message. When it calls output( ), the mutex prevents multiple threads from writing to this Display object. (You must use only one Display object in your program, as you’ll see in the following examples.)
This just shows the basic idea, but if necessary, you can build a more elaborate framework. For example, you could enforce the requirement that there be only one Display object in a program by making it a Singleton. (The ZThread library has a Singleton template to support Singletons.)
The ornamental garden
In this simulation, the garden committee would like to know how many people enter the garden each day though its multiple gates. Each gate has a turnstile or some other kind of counter, and after the turnstile count is incremented, a shared count is incremented that represents the total number of people in the garden.
//: C11:OrnamentalGarden.cpp
//{L} ZThread
#include "Display.h"
#include "zthread/Thread.h"
#include "zthread/FastMutex.h"
#include "zthread/Guard.h"
#include "zthread/ThreadedExecutor.h"
#include "zthread/CountedPtr.h"
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace ZThread;
using namespace std;
class Count : public Cancelable {
FastMutex lock;
int count;
bool paused, canceled;
public:
Count() : count (0), paused(false), canceled(false) {
srand(time(0)); // Seed the random number generator
}
int increment() {
// Comment the following line to see counting faiclass="underline"
Guard<FastMutex> g(lock);
int temp = count ;
if(rand() < RAND_MAX/2) // Yield half the time
Thread::yield();
return (count = ++temp);
}
int value() {
Guard<FastMutex> g(lock);
return count;
}
void cancel() {
Guard<FastMutex> g(lock);
canceled = true;
}
bool isCanceled() {
Guard<FastMutex> g(lock);
return canceled;
}
bool pause() {
Guard<FastMutex> g(lock);
paused = true;
}
bool isPaused() {
Guard<FastMutex> g(lock);
return paused;
}
};
class Entrance : public Runnable {
CountedPtr<Count> count;
CountedPtr<Display> display;
int number;
int id;
bool waitingForCancel;
public:
Entrance(CountedPtr<Count>& cnt,
CountedPtr<Display>& disp, int idn)
: count(cnt), display(disp), id(idn), number(0),
waitingForCancel(false) {}
void run() {
while(!count->isPaused()) {
number++;
{
ostringstream os;
os << *this << " Totaclass="underline" "
<< count->increment() << endl;
display->output(os);
}
Thread::sleep(100);
}
waitingForCancel = true;
while(!count->isCanceled()) // Hold here...
Thread::sleep(100);
ostringstream os;
os << "Terminating " << *this << endl;
display->output(os);
}
int getValue() {
while(count->isPaused() && !waitingForCancel)
Thread::sleep(100);
return number;
}
friend ostream&
operator<<(ostream& os, const Entrance& e) {
return os << "Entrance " << e.id << ": " << e.number;
}
};
int main() {
cout << "Press <ENTER> to quit" << endl;
CountedPtr<Count> count(new Count);
vector<Entrance*> v;
CountedPtr<Display> display(new Display);
const int sz = 5;
try {
ThreadedExecutor executor;
for(int i = 0; i < sz; i++) {
Entrance* task = new Entrance(count, display, i);
executor.execute(task);
// Save the pointer to the task:
v.push_back(task);
}
cin.get(); // Wait for user to press <Enter>
count->pause(); // Causes tasks to stop counting
int sum = 0;
vector<Entrance*>::iterator it = v.begin();
while(it != v.end()) {
sum += (*it)->getValue();
it++;
}
ostringstream os;
os << "Totaclass="underline" " << count->value() << endl
<< "Sum of Entrances: " << sum << endl;
display->output(os);
count->cancel(); // Causes threads to quit
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
Count is the class that keeps the master count of garden visitors. The single Count object defined in main( ) as count is held as a CountedPtr in Entrance and thus is shared by all Entrance objects. A FastMutex called lock is used in this example instead of an ordinary Mutex because a FastMutex uses the native operating system mutex and will thus yield more interesting results.
A Guard is used with lock in increment( ) to synchronize access to count. This function uses rand( ) to cause a yield( ) roughly half the time, in between fetching count into temp and incrementing and storing temp back into count. Because of this, if you comment out the Guard object definition, you will rapidly see the program break because multiple threads will be accessing and modifying count simultaneously.