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) |