163 / 163 · C11 · 8 min
Monitors
This appendix presents monitors as a construct that packages shared data with its access operations into one module while automatically supplying mutual exclusion, uses condition variables for wait-and-signal, and contrasts Hoare versus Mesa semantics as they appear in real implementations.
In this lesson
How a Monitor Automatically Guarantees Mutual Exclusion
A monitor places shared variables together with the methods that manipulate them inside a single module. Entry into any method implicitly acquires the module lock and exit releases it, so at most one thread ever executes inside the module, eliminating concurrent modification of the shared data.
Condition Variables Must Be Paired with Explicit State
A lock alone is insufficient; threads also need to sleep when a buffer is full or empty. Monitors therefore supply condition variables, yet those variables hold no count of their own, so an extra integer state variable must be maintained and inspected before a thread decides to wait.
Hoare Semantics Switch Immediately while Mesa Semantics Only Hint
Under Hoare semantics a signal instantly hands the lock and control to one waiter and the signaller pauses. Mesa semantics treat signal as a hint: the signaller keeps running until it leaves the module and the waiter is scheduled later, so the waiter must re-test the condition after waking.
Evolution from Theory into Language Features
The original theoretical design favoured ease of proof; later systems languages switched to Mesa style for simpler implementation. Languages such as Java now offer a close analogue via synchronized methods, yet programmers must still use wait/notify together with loop checks.
Pitfalls
- Using if instead of while to test the condition under Mesa semantics, so a thread proceeds after another thread has already invalidated the condition.
- Treating a condition variable like a semaphore and omitting an independent state counter, which produces permanent waits or incorrect wake-ups.
Run an example
Minimum C11 · complete program · Download .c
#include <stdio.h>
#include <pthread.h>
int balance = 0;
pthread_mutex_t mon = PTHREAD_MUTEX_INITIALIZER;
void deposit(int n) {
pthread_mutex_lock(&mon);
balance += n;
pthread_mutex_unlock(&mon);
}
void *worker(void *arg) { (void)arg;
for (int i = 0; i < 1000; i++)
deposit(1);
return NULL;
}
int main(void) {
pthread_t t1, t2;
pthread_create(&t1, NULL, worker, NULL);
pthread_create(&t2, NULL, worker, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("%d\n", balance);
return 0;
}
Compile locally
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror -pthread ostep-monitors.c -o example && ./exampleExpected result
2000
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
Why must a bounded-buffer monitor that uses Mesa semantics write the “is the buffer empty” test as a while loop?
Show a reference answer
Because signal only moves the waiter to the ready queue and does not guarantee immediate execution; another consumer may empty the buffer before the original waiter re-acquires the lock, so the state must be re-checked.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.