REVIEW OF MONITORS (Gene Cooperman) pthread_mutex_lock ___________ _______________ | | | pthread_cond_wait | | | | ______ | | | | | | |<-+---- "jail" | | | | | ------- | | | | pthread_cond_signal | | | | | ----------- --------------- pthread_mutex_unlock The "jail" is associated with a condition variable, and a "condition" (code) that should be tested by any thread upon trying to leave the jail. Well-structured code should follow this rule, but the system does not enforce the rule. 1. Single multex "defines" the boundaries of the monitor. 2. The pthread_cond_XXX functions should be called only inside the monitor. 3. pthread_cond_wait() will send thread to "jail" 4. pthread_cond_signal() will wake up a thread in the "jail", but it must still wait on the mutex before actually exiting the jail. Only one thread is allowed in the monitor -- exclusive of the jail. 5. Spurious "wakeup" from the jail can occur. A thread in the jail is in the SLEEPING state of Linux. As always, if a signal is received (e.g., SIGWINCH when a user changes the size of a terminal), then the thread will change state from SLEEPING to RUNNABLE. 6. Because of the possibility of spurious wakeups, a thread leaving the jail should first test if it has a right to be outside the jail, before doing anything else. It should do so, by executing the "condition code" associated with the "condition variable" that is in turn associated with the "jail". 7. INTUITION: a. A mutex protects the monitor. b. Inside the monitor, we can "check out a resource", or we can "return it". c. Ideally, we should spend very little time inside the monitor. We do not want to block another thread. d. Conclusion: After checking out a resource, we should leave the monitor, and not use it inside the monitor. Generalization: We can have two jails inside the monitor. It just requires defining two condition variables. This is useful if we want to define two different codes for testing "conditions". Example: Reader-writer: Define two global variables, protected by a mutex: num_readers num_writers Using the principles above, the code is obvious: read_acquire() mutex_lock() // while instead of if: check for spurios wakeup while (num_writers > 0) pthread_cond_wait() num_readers++ mutex_unlock() read_release() mutex_lock() num_readers-- if (num_readers == 0) pthread_cond_signal() // wake up a writer mutex_unlock() [ and similarly for writers ] Note that we could add another global variable, num_writers_in_queue. This allows us to generate a variant: writer-preferred If a reader sees that writers_in_queue > 0, then it calls pthread_cond_wait() on itself. Finally, note that some of the code might simplify in some variations of the reader-writer problem if we have two "jails": one for readers; and one for writers. NOTES: A. The example code is almost reader-preferred. i. To make it fully reader-preferred, add a variable num_readers_waiting_to_enter. Then protect it by a separate mutex. Readers should update it, and a writer should check this variable as part of its condition code. ii. To make it writer-preferred, add a variable num_writers_in_jail. Then have readers check the variable as part of their condition code. B. pthread_cond_broadcast() exists. One can always replace pthread_cond_signal() by pthread_cond_broadcast() without loss of correctness if threads check their condition code on exit from jail. However, it is less efficient to wake up all threads that are in jail. C. 'man pthread_cond_signal' states: "If more than one thread is blocked on a condition variable, the scheduling policy shall determine the order in which threads are unblocked." It would be unsafe to rely on a particular scheduling policy such as FIFO, since spurious wakeups may cause the ordering of the threads in the jail to be changed.