Recall that a monitor has some local variables, monitor procedures and an initialization part. When the monitor is created, the initialization part initializes the local variables. The boundary of the monitor serves as a fence for blocking threads that call one of the monitor procedures. More precisely, at any time, there can only be one thread executing within the monitor boundary. Therefore, the monitor boundary guarantees mutual exclusion.
To simulate a monitor using C, we need to address a three questions: (1) how do we make sure that the local variables will not be accessed by non-monitor procedures, (2) how do we make sure that the user can only ``see'' the interface without knowing the details of the monitor, and (3) how do we properly setup the monitor boundary so that mutual exclusion can be guaranteed.
Questions (1) and (2) have a simple solution. We could use two files, one header file that stores the prototypes of the monitor procedures and a implementation file in which the local variables and all monitor procedures that are not supposed to be used by the user directly are declared with static storage class. A name declared with static can only be accessed from the place after the declaration point in the ``same'' file. Question (3) has a natural answer: use a mutex lock!
To illustrate this idea, we shall design a monitor for maintaining a counter so that its value can be increased, decreased, reset and retrieved. In addition to this, we also need an initialization function. Thus, we need a header file in which the prototype of these five functions are defined. Let the increasing, decreasing, resetting and retrieving functions be INC(), DEC(), SET() and GET(), respectively. Function SET() has an argument for setting the counter to this new value. The initialization function is CounterInit() which takes the initial value for the counter as its only argument. Let the file that contains these information be inc-dec-m.h.
Click here to download a copy of this header file.
void CounterInit(int n); /* initialize monitor */ int INC(void); /* increase the counter */ int DEC(void); /* decrease the counter */ void SET(int n); /* reset the counter */ int GET(void); /* retrieve counter's value */
Next, we shall design all functions and local variables of the monitor and put them in an implementation file. Let this file be inc-dec-m.c.
Click here to download a copy of this file.
#include <thread.h> #include <synch.h> static int count; /* the static counter */ static mutex_t MonitorLock; /* the static mutex lock */ void CounterInit(int n) { count = n; mutex_init(&MonitorLock, USYNC_THREAD, (void *) NULL); } int INC(void) { int value; mutex_lock(&MonitorLock); /* lock the monitor */ value = (++count); /* increase and save counter*/ mutex_unlock(&MonitorLock); /* release the monitor */ return value; /* return the new value */ } int DEC(void) { int value; mutex_lock(&MonitorLock); value = (--count); mutex_unlock(&MonitorLock); return value; } void SET(int n) { mutex_lock(&MonitorLock); count = n; mutex_unlock(&MonitorLock); } int GET(void) { int value; mutex_lock(&MonitorLock); value = count; mutex_unlock(&MonitorLock); return value; }
Here are further notes of this monitor.
Suppose when INC() was called the value of count is 2. Then, the monitor is locked, the value of count is changed to 3, and the monitor is unlocked. At this moment, we would expect INC() returns 3. Unfortunately, it may not be the case. Before executing the return statement, another thread calls INC() (or DEC() or SET()) and has the value of count changed, this call to INC() will not return 3 but some other unexpected values. This is why the new counter value is immediately saved to value, which is returned after exiting the monitor.int INC(void) { int value; mutex_lock(&MonitorLock); ++count; mutex_unlock(&MonitorLock); return count; }
Let us write a main program to use this monitor. In fact, this main program has no mystery at all. The monitor is initialized by a call like CounterInit(0) to put a zero into the monitor protected counter. In addition to a monitor, two mutex locks, Screen and RandomNumber are introduced. The former protects the screen and the latter guarantees that the random number generator can be accessed mutual exclusively.
In this main program, each ``increasing'' thread increases the counter value MAX_ITEMS times. On the other hand, each ``decreasing'' thread decreases the counter value MAX_ITEMS times. The ``resetting'' thread resets the counter value MAX_ITEMS/2 times using random numbers and the ``display'' thread displays the counter value MAX_ITEMS/2 times. The main program creates equal number of ``increasing'' and ``decreasing'' threads, one ``resetting'' thread and ``display'' thread. This is a busy program!
Click here to download a copy of this main program (counter-2.c).
#include <stdio.h> #include <thread.h> #include <stdlib.h> #include <time.h> #include "inc-dec-m.h" #define MAX_ITEMS 20 #define MAX_INC 5 #define MAX_DEC 4 #define MAX_THREADS 3 mutex_t Screen; /* mutex lock for screen */ mutex_t RandomNumber; /* lock for random # gen. */ void *Increase(void *voidPTR) { int *intPTR = (int *) voidPTR; int ID = *intPTR; int value, i; for (i = 0; i < MAX_ITEMS; i++) { /* iterates MAX_ITEMS times */ thr_yield(); /* take some rest */ value = INC(); /* increase the value */ mutex_lock(&Screen); /* display the new value */ printf(" (%d): Counter has been increased by 1 " "(value = %d)\n", ID, value); mutex_unlock(&Screen); } thr_exit(0); } void *Decrease(void *voidPTR) { int *intPTR = (int *) voidPTR; int ID = *intPTR; int value, i; for (i = 0; i < MAX_ITEMS; i++) { thr_yield(); value = DEC(); mutex_lock(&Screen); printf(" (%d): Counter has been decreased by 1 " "(value = %d)\n", ID, value); mutex_unlock(&Screen); } thr_exit(0); } void *Reset(void *voidPTR) { int *intPTR = (int *) voidPTR; int ID = *intPTR; int value, i; for (i = 0; i < MAX_ITEMS/2; i++) { thr_yield(); mutex_lock(&RandomNumber); value = rand() % MAX_INC; mutex_unlock(&RandomNumber); SET(value); mutex_lock(&Screen); printf("*** (%d): The counter has been reset to %d ***\n", ID, value); mutex_unlock(&Screen); } thr_exit(0); } void *Display(void *voidPTR) { int *intPTR = (int *) voidPTR; int ID = *intPTR; int value, i; for (i = 0; i < MAX_ITEMS/2; i++) { thr_yield(); value = GET(); mutex_lock(&Screen); printf("### (%d): The current counter value is %d ###\n", ID, value); mutex_unlock(&Screen); } thr_exit(0); } void main(void) { thread_t incID[MAX_THREADS], decID[MAX_THREADS]; thread_t ResetID, DisplayID; size_t incStatus[MAX_THREADS], decStatus[MAX_THREADS]; size_t ResetStatus, DisplayStatus; int incArg[MAX_THREADS], decArg[MAX_THREADS]; int ResetArg, DisplayArg; int i; srand((unsigned) time(NULL)); mutex_init(&Screen, USYNC_THREAD, (void *) NULL); mutex_init(&RandomNumber, USYNC_THREAD, (void *) NULL); CounterInit(0); ResetArg = 0; thr_create(NULL, 0, Reset, (void *) &ResetArg, 0, (void *) &ResetID); DisplayArg = 1; thr_create(NULL, 0, Display, (void *) &DisplayArg, 0, (void *) &DisplayID); for (i = 0; i < MAX_THREADS; i++) { incArg[i] = i + 2; decArg[i] = incArg[i] + MAX_THREADS; thr_create(NULL, 0, Increase, (void *) &(incArg[i]), 0, (void *) &(incID[i])); thr_create(NULL, 0, Decrease, (void *) &(decArg[i]), 0, (void *) &(decID[i])); } for (i = 0; i < MAX_THREADS; i++) { thr_join(incID[i], 0, (void *) &(incStatus[i])); thr_join(decID[i], 0, (void *) &(decStatus[i])); } thr_join(ResetID, 0, (void *) &ResetStatus); thr_join(DisplayID, 0, (void *) &DisplayStatus); printf("Parent exits ...\n"); }