100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C

Monitors

Learn how monitors bundle shared data, procedures, and condition variables into a higher-level synchronization construct.

Process SynchronizationIntermediate10 min readJul 8, 2026
Analogies

Introduction

Semaphores are powerful but low-level and error-prone: a single misplaced or forgotten sem_wait/sem_post call can cause deadlock or allow a race condition, and there is no language-level enforcement tying the semaphore to the data it protects. A monitor is a higher-level synchronization construct that packages shared data together with the procedures that operate on it, guaranteeing that at most one thread executes inside the monitor's procedures at a time, and provides condition variables for waiting on specific conditions.

🏏

Cricket analogy: Relying on every fielder to individually remember to return to position (like sem_wait/sem_post) is error-prone; a monitor is like a fielding captain who automatically resets positions between overs, bundling the field layout with the enforcement rule.

Explanation

A monitor automatically provides mutual exclusion: entering any procedure of the monitor implicitly acquires a lock, and leaving releases it, so the programmer cannot forget to unlock as they might with a raw semaphore. Because pure mutual exclusion is not always enough — a thread often needs to wait for some condition to become true (e.g., 'buffer not empty') — monitors add condition variables, each supporting two operations: wait(), which releases the monitor lock and suspends the calling thread until another thread signals the condition, and signal() (or notify), which wakes one thread waiting on that condition. Crucially, a condition variable is not a counter like a semaphore: if signal() is called with no thread waiting, the signal has no effect and is simply lost (it is not remembered for a future wait()), whereas a semaphore's sem_post() always increments its count. POSIX threads do not have a built-in 'monitor' keyword, but the standard idiom pthread_mutex_t + pthread_cond_t implements the same pattern: acquire the mutex, loop while (!condition) pthread_cond_wait(&cond, &mutex); (which atomically releases the mutex while waiting and re-acquires it on wake), then proceed, and use pthread_cond_signal/pthread_cond_broadcast to wake waiters after changing the condition.

🏏

Cricket analogy: Stepping into the striker's crease automatically means you're 'on strike' (implicit lock acquisition) and stepping out releases it; if the team must wait for rain to stop (a condition), they wait() and only resume when someone signals 'play has resumed.'

Example

c
#include <stdio.h>
#include <pthread.h>

#define BUFFER_SIZE 5

int buffer[BUFFER_SIZE];
int count = 0, in = 0, out = 0;

pthread_mutex_t mon_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  not_full  = PTHREAD_COND_INITIALIZER;
pthread_cond_t  not_empty = PTHREAD_COND_INITIALIZER;

void monitor_put(int item) {
    pthread_mutex_lock(&mon_lock);
    while (count == BUFFER_SIZE) {           /* condition: buffer full */
        pthread_cond_wait(&not_full, &mon_lock);
    }
    buffer[in] = item;
    in = (in + 1) % BUFFER_SIZE;
    count++;
    pthread_cond_signal(&not_empty);          /* wake a waiting consumer */
    pthread_mutex_unlock(&mon_lock);
}

int monitor_get(void) {
    pthread_mutex_lock(&mon_lock);
    while (count == 0) {                      /* condition: buffer empty */
        pthread_cond_wait(&not_empty, &mon_lock);
    }
    int item = buffer[out];
    out = (out + 1) % BUFFER_SIZE;
    count--;
    pthread_cond_signal(&not_full);           /* wake a waiting producer */
    pthread_mutex_unlock(&mon_lock);
    return item;
}

Analysis

monitor_put and monitor_get together implement the same bounded-buffer behavior as the semaphore-based Producer-Consumer solution, but with a different structure: the mutex enforces that only one call to monitor_put/monitor_get runs at a time (mutual exclusion built into the abstraction), and pthread_cond_wait cleanly expresses 'block until this specific condition holds' without manually juggling counting semaphores. The while loop (rather than if) around the condition check is essential because pthread_cond_wait can return due to a spurious wakeup or because another thread got there first and changed the condition again, so the condition must always be re-checked after waking, before proceeding.

🏏

Cricket analogy: Two teams sharing a single practice net implement turn-taking not by counting slots like semaphores, but by one team calling monitor_put (dropping gear) and the other monitor_get (picking it up), with the coach's while-loop check re-verifying 'net actually free' before entering, since a false alarm can occur.

Key Takeaways

  • A monitor bundles shared data, its operations, and synchronization into one construct, automatically enforcing mutual exclusion on entry/exit.
  • Condition variables provide wait()/signal() so threads can block until an application-specific condition becomes true.
  • Unlike a semaphore, a signal on a condition variable is lost if no thread is currently waiting — it is not counted or remembered.
  • POSIX implements the monitor pattern with pthread_mutex_t plus pthread_cond_t and pthread_cond_wait/signal/broadcast.
  • Always re-check the wait condition in a while loop after pthread_cond_wait returns, to guard against spurious wakeups and stale conditions.

Practice what you learned

Was this page helpful?

Topics covered

#OperatingSystemsStudyNotes#OperatingSystems#Monitors#Explanation#Example#Analysis#Key#StudyNotes#SkillVeris#ExamPrep