ThreadMentor defines a base class Monitor. All user monitors must be derived classes of this base class. In a user monitor class, we can declare monitor procedures as public methods, put the initialization section into constructors, and make monitor procedures as methods of the derived class.
In the following, we define a monitor class MyMonitor as a derived class of Monitor. This monitor has its initialization in its only constructor, and has three publicly available monitor procedures GetData(), PutData() and Display(). The private items can only be accessed from within the monitor. They consist of an integer variable Volume and a private procedure isSafe(). Note that no thread can call method isSafe(); however, they can call it after they have entered the monitor (by executing a monitor procedure).
#include "ThreadClass.h" class MyMonitor : public Monitor { public: MyMonitor(int); // initialization -- constructor int GetData(void); // monitor procedure void PutData(int); // monitor procedure void Display(void); // monitor procedure private: int Volume; // private data int isSafe(int); // private procedure };
The next important task in writing a monitor is to design those monitor procedures. A monitor procedure is simply a C++ function. However, to guarantee mutual exclusion of a monitor, ThreadMentor requires that a monitor procedure calls method MonitorBegin() before performing any operation and calls method MonitorEnd() right before leaving a monitor procedure. Methods MonitorBegin() and MonitorBegin() are defined in the base class Monitor. Without calling these two methods, mutual exclusion is not guaranteed. In fact, the execution of method MonitorBegin() locks the monitor for the calling thread, and the execution of method MonitorEnd() unlocks the monitor so that one of the waiting threads can enter.
The method GetData() of monitor MyMonitor as shown below is a very simple example. This method simply returns the value of the private variable Volume. Before performing any task, monitor procedure GetData() calls method MonitorBegin() to lock the monitor so that this is the only thread executing in the monitor. Then, GetData() saves the current value of variable Volume to its local variable Temp. Before it exits, method MonitorEnd() is called to release the monitor so that another thread can execute a monitor procedure. After this, method GetData() returns.
int GetData::MyMonitor(void) { int Temp; MonitorBegin(); // do something Temp = Volume; MonitorEnd(); return Temp; }
But, why is the variable Temp necessary? If we replace the above by the following one, after the calling thread executes MonitorEnd() and before it executes return Volume, because the monitor is no more locked (or "owned") by the calling thread, another thread may enter the monitor and changes the value of Volume, and, as a result, the value of Volume returned by this thread may not be the original value when this thread was in the monitor. This is the reason that we save the value of variable Volume in a local variable so that it can returned properly.
int GetData::MyMonitor(void) { int Temp; MonitorBegin(); // do something MonitorEnd(); return Volume; }
Note that we did not discuss how to write a constructor, because we need to learn more on a later page.
Some may argue that the following solution is better because (1) it does not require a temporary variable, and (2) it can return the value of Volume properly. It is correct up to this point; however, the problem is that the lock of the monitor is never released. More precisely, the execution of MonitorBegin() locks the monitor for the calling thread so that mutual exclusion is guaranteed. However, the execution returns at the return statement. As a result, the execution flow returns to the calling thread and the method MonitorEnd() is never called. The consequence is that this thread still "holds" the lock of the monitor and no other thread can enter. Note that this thread cannot enter the monitor by calling a monitor procedure either , because the monitor is locked and ThreadMentor does not support recursive lock (i.e., acquiring the same lock by the owner more than once). So, basically, the monitor becomes un-usable!
int GetData::MyMonitor(void) { MonitorBegin(); // do something return Volume; MonitorEnd(); }
Constructors of a monitor are used to initialize local variables of that monitor. However, since a monitor is a derived class of the base class Monitor, we should also initialize the base class. The base class requires two arguments: the name and the type of the monitor that is being initialized. The name of a monitor is simply a string. The type of a monitor can only take one of the two possible values: HOARE and MESA. The type of a monitor is discussed on Monitor Types. Meanwhile, it is irrelevant. The following is a constructor example:
MyMonitor::MyMonitor(char *Name, other arguments) : Monitor(Name, HOARE) { // initialization }
This constructor receives the name of a monitor and other arguments, and initialize the base class with Monitor(Name, HOARE). Thus, the type of this monitor is HOARE. By default, the name and type of a monitor are Noname and HOARE, respectively.