Suppose we have two groups of threads and a shared counter. Threads in the first group increase the value of the shared counter by 1 repeatedly, while threads in the second group decreases the counter's value by 1. This can easily be done with a single Mutex lock. However, this requires every thread to follow the same lock-unlock protocol, and, if one of these thread does it wrong, mutual exclusion cannot be guaranteed. Let us do it using a monitor.
Since mutual exclusion of a monitor is established by methods MonitorBegin() and MonitorEnd(), we can move the shared counter into a monitor as a private data item, and design monitor procedures Increment() to increase the counter by 1 and Decrement() to decrease the counter value by 1.
Our monitor definition is shown below. The name of this monitor is CounterMonitor. It has a constructor, two public monitor procedures Increment() and Decrement(), and a private variable counter. Just like threads we have the monitor definition in a header .h file and its implementation in a .cpp file with the same name. Therefore, the monitor definition is in the file IncDec-mon.h.
#include "ThreadClass.h"
class CounterMonitor : public Monitor
{
public:
CounterMonitor(char* Name); // constructor
int Increment(); // increment the counter
int Decrement(); // decrement the counter
private:
int counter; // internal counter
};
|
| Click here to download this file (IncDec-mon.h) |
Let use see how the monitor procedures are implemented. The constructor is simple. It sets the monitor name, makes the monitor of Hoare style (i.e., argument HOARE), and clears variable counter. Monitor procedure Increment() first locks the monitor by calling MonitorBegin(). Once the monitor is locked (i.e., the calling thread is in the monitor), the counter value can be safely increased. Note that the new value is saved in a local variable number so that it can be returned to the calling thread properly. After this and before leaving the monitor, method MonitorEnd() is called to unlock the monitor for other threads to get in. Finally, the value of the local variable number is returned. Note that this is the value after this thread's increment. The monitor procedure Decrement() is similar and will not be discussed. The two procedures and the constructor are saved in a file IncDec-mon.cpp as shown below. Now, our monitor implementation completes.
#include <iostream.h>
#include "ThreadClass.h"
#include "IncDec-mon.h"
CounterMonitor::CounterMonitor(char* Name)
: Monitor(Name, HOARE)
{
counter = 0; // clear the counter
}
int CounterMonitor::Increment()
{
int number;
MonitorBegin(); // lock the monitor
counter++; // increase the counter
number = counter; // save its value
MonitorEnd(); // release the monitor
return number; // return the counter value
}
int CounterMonitor::Decrement()
{
int number;
MonitorBegin(); // lock the monitor
counter--; // decrease the counter
number = counter; // save its value
MonitorEnd(); // release the monitor
return number; // return the counter value
}
|
| Click here to download this file (IncDec-mon.cpp) |
We should see how threads can use these two monitor procedures. The thread class is simple. The char variable Id is used to distinguish if a thread will increase the counter. This status is assigned by the main program when threads are created.
#include "ThreadClass.h"
class IncDecThread: public Thread
{
public:
IncDecThread(int No, char ID); // constructor
private:
void ThreadFunc();
int number;
char Id; // Inc or Dec key
};
|
| Click here to download this file (IncDec-Thrd.h) |
The thread itself is also simple. It loops for NUM_OF_TIMES iterations. For each iteration, based on the assigned role, it either increases the counter value by calling method Counter.Increment() or decreases the counter value by calling method Counter.Decrement(). Then, the new value after an operation is performed on the counter is printed. It is clear from this example that, compared with the use of semaphores, the use of monitor makes our code more structured and centralized.
#include <iostream.h>
#include "ThreadClass.h"
#include "IncDec-Thrd.h"
#include "IncDec-mon.h"
#define NUM_OF_TIMES 10 // maximum number of iterations
// static data variables
static CounterMonitor Counter("CounterMonitor");
strstream *Filler(int n)
{
int i;
strstream *Space;
Space = new strstream;
for (i = 0; i < n; i++)
(*Space) << ' ';
(*Space) << '\0';
return Space;
}
IncDecThread::IncDecThread(int No, char ID)
: number(No), Id(ID)
{
ThreadName.seekp(0, ios::beg);
if (ID == 'I') // an increment thread
ThreadName << "IncThread" << No << '\0';
else
ThreadName << "DecThread" << No << '\0';
}
void IncDecThread::ThreadFunc()
{
Thread::ThreadFunc();
int localCounter;
strstream *Space;
Space = Filler(this->number);
for (int i = 1; i <= NUM_OF_TIMES; i++) {
Delay();
if (Id == 'I') { // increase the counter
localCounter = Counter.Increment();
cout << Space->str() << ThreadName.str() <<
" increment the counter to " << localCounter << endl;
}
else { // decrease the counter
localCounter = Counter.Decrement();
cout << Space->str() << ThreadName.str() <<
" decrement the counter to " << localCounter << endl;
}
}
cout << Space->str() << ThreadName.str() << " Exit" << endl;
Exit();
}
|
| Click here to download this file (IncDec-Thrd.cpp) |
The main program creates a number of threads. Before a thread is created, the main program uses a random number to determine if this thread is of an increment type or a decrement type. After a thread is created, it is run. After all threads are created, the main program waits for the completion of all threads.
#include <iostream.h>
#include <stdlib.h>
#include <time.h>
#include "ThreadClass.h"
#include "IncDec-Thrd.h"
#include "IncDec-mon.h"
#define MAX_NUM 20 // maximum number of inc/dec threads
int main(int argc, char *argv[])
{
IncDecThread *thread[MAX_NUM];
int NumberOfIncThreads = 0, // number of increment threads
NumberOfDecThreads = 0; // number of decrement threads
int NumberOfThreads; // total number of threads
int i;
if (argc != 2) { // verify user input
cout << "Usage " << argv[0] << " #-of-Threads" << endl;
exit(0);
}
else
NumberOfThreads = abs(atoi(argv[1]));
srand((unsigned int) time(NULL)); // initialize random seed
// create the increment/decrement threads in a random way
for (i = 0; i < NumberOfThreads; i++) { // create threads
if (rand() % 10 <= 5) { // create increment thread
NumberOfIncThreads++;
thread[i] = new IncDecThread(NumberOfIncThreads, 'I');
thread[i]->Begin();
}
else { // create decrement thread
NumberOfDecThreads++;
thread[i] = new IncDecThread(NumberOfDecThreads, 'D');
thread[i]->Begin();
}
}
for (i = 0; i < NumberOfThreads; i++) // joining
thread[i]->Join();
Exit();
}
|
| Click here to download this file (IncDec-main.cpp) |
|
|
|