Quiz 2

Process Synchronization — Mutex, Semaphores, Monitors

1403 words
7 min read
Python Week 1: the first filter for runtime behavior
Visual companion
Python
Type and operator map

Python Week 1: the first filter for runtime behavior

View
Revision summary

What this note is really saying

Short form

# Process Synchronization — Mutex, Semaphores, Monitors ## 🎯 Learning Objectives - Explain the critical section problem and its three requirements - Implement mutual exclusion using mutex locks and semaphores - Differentiate between counting and binary semaphores - Solve classic synchronization problems using semap...

Process Synchronization — Mutex, Semaphores, Monitors

🎯 Learning Objectives

  • Explain the critical section problem and its three requirements
  • Implement mutual exclusion using mutex locks and semaphores
  • Differentiate between counting and binary semaphores
  • Solve classic synchronization problems using semaphores and monitors
  • Understand the ABA problem and spinlock tradeoffs

1. The Critical Section Problem

1.1 Intuition

When multiple threads access shared data concurrently, race conditions can produce incorrect results. The critical section is the code region where shared resources are accessed — we need to ensure only one thread enters it at a time.

1.2 Race Condition Example

c
int counter = 0;
// Thread 1                    // Thread 2
counter++;                     counter++;
// Load counter (0)            // Load counter (0)
// Increment to 1              // Increment to 1
// Store counter (1)           // Store counter (1)
Expected: counter = 2. Actual: counter = 1. Both threads read 0 before either writes 1.

1.3 Critical Section Requirements

RequirementMeaning
Mutual ExclusionOnly one process can be in its critical section at a time
ProgressIf no process is in its critical section, only processes that are outside their entry sections can decide who enters next
Bounded WaitingThere is a limit on how many times other processes can enter after a process requests entry
(Diagram)

2. Mutex Locks

2.1 Intuition

A mutex (mutual exclusion) is like a bathroom key — only one person can hold it at a time. If you don't have the key, you wait outside.

2.2 Implementation

c
#include <pthread.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void* thread_function(void* arg) {
    pthread_mutex_lock(&lock);    // Entry section — acquire lock
    // Critical section
    shared_data++;
    pthread_mutex_unlock(&lock);  // Exit section — release lock
    return NULL;
}

2.3 Types of Mutex

TypeBehavior
Normal (fast)Deadlock if same thread locks twice
RecursiveSame thread can lock multiple times (counted)
Error-checkReturns error on invalid operations
AdaptiveSpins for a short time before sleeping

2.4 Spinlock vs Mutex

(Diagram)
AspectSpinlockMutex (Sleeping Lock)
CPU usageHigh (busy-wait)Low (sleeps)
Latency when lock releasedLowHigh (context switch)
Best forShort CSLong CS
Interrupt contextSafeNot safe (can't sleep)

3. Semaphores

3.1 Intuition

A semaphore is a more general synchronization primitive than a mutex. It's like a parking lot with S parking spots — cars (threads) enter until capacity is reached, then they wait.

3.2 Abstract Definition

A semaphore S is an integer variable accessed through two atomic operations:
  • wait(S) (P, down): Decrement S; if S < 0, block
  • signal(S) (V, up): Increment S; if S ≤ 0, wake a blocked process
c
// Binary semaphore (like mutex)
sem_t sem;
sem_init(&sem, 0, 1);   // Initialize to 1
sem_wait(&sem);          // Entry section
// Critical section
sem_post(&sem);          // Exit section
// Counting semaphore — control access to N resources
sem_t pool;
sem_init(&pool, 0, N);  // N available resources
sem_wait(&pool);         // Acquire one resource
// Use resource
sem_post(&pool);         // Release resource

3.3 Binary vs Counting Semaphores

AspectBinary SemaphoreCounting Semaphore
Value range0 or 10 to N
Initial valueTypically 1Any non-negative integer
Use caseMutual exclusionResource pool management
Same as mutex?Similar, no ownership tracking

3.4 Worked Example: Producer-Consumer

c
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int in = 0, out = 0;
sem_t empty;  // Counts empty slots
sem_t full;   // Counts full slots
sem_t mutex;  // Binary semaphore for mutual exclusion
void init() {
    sem_init(&empty, 0, BUFFER_SIZE);
    sem_init(&full, 0, 0);
    sem_init(&mutex, 0, 1);
}
void* producer(void* arg) {
    int item;
    while (1) {
        item = produce_item();
        sem_wait(&empty);       // Wait for empty slot
        sem_wait(&mutex);       // Lock buffer
        buffer[in] = item;
        in = (in + 1) % BUFFER_SIZE;
        sem_post(&mutex);       // Unlock buffer
        sem_post(&full);        // Signal buffer is full
    }
}
void* consumer(void* arg) {
    int item;
    while (1) {
        sem_wait(&full);        // Wait for full slot
        sem_wait(&mutex);       // Lock buffer
        item = buffer[out];
        out = (out + 1) % BUFFER_SIZE;
        sem_post(&mutex);       // Unlock buffer
        sem_post(&empty);       // Signal empty slot
        consume_item(item);
    }
}
Tracing (buffer size 3, initial state):
StepProducer ActionConsumer ActionemptyfullmutexBuffer
0Initialize301[_, _, _]
1wait(empty)→2, wait(mutex)→0200
2Produce item A, post(mutex)→1201[A, _, _]
3post(full)→1211[A, _, _]
4wait(full)→0, wait(mutex)→0200
5Consume A, post(mutex)→1201[_, _, _]
6post(empty)→3301[_, _, _]

4. Monitors

4.1 Intuition

A monitor is a high-level synchronization construct that automatically ensures mutual exclusion. Only one thread can be active inside a monitor at a time. Condition variables allow threads to wait for specific conditions inside the monitor.

4.2 Monitor Structure

c
#include <pthread.h>
typedef struct {
    int items;                // Shared state
    pthread_mutex_t mutex;    // For mutual exclusion
    pthread_cond_t cond;      // Condition variable
} Monitor;
void monitor_function(Monitor* m) {
    pthread_mutex_lock(&m->mutex);
    // Automatically exclusive access
    while (!condition) {
        pthread_cond_wait(&m->cond, &m->mutex);  // Release mutex, sleep
    }
    // Critical section
    pthread_cond_signal(&m->cond);  // Wake one waiting thread
    pthread_mutex_unlock(&m->mutex);
}

4.3 Condition Variables

FunctionDescription
pthread_cond_wait(cond, mutex)Atomically release mutex and wait; on wake, reacquire mutex
pthread_cond_signal(cond)Wake one waiting thread
pthread_cond_broadcast(cond)Wake all waiting threads

4.4 Monitor Example: Bounded Buffer

c
#include <pthread.h>
#define N 10
int buffer[N];
int count = 0, in = 0, out = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
void put(int item) {
    pthread_mutex_lock(&mutex);
    while (count == N)
        pthread_cond_wait(&not_full, &mutex);  // Wait until not full
    buffer[in] = item;
    in = (in + 1) % N;
    count++;
    pthread_cond_signal(&not_empty);  // Wake consumer
    pthread_mutex_unlock(&mutex);
}
int get() {
    pthread_mutex_lock(&mutex);
    while (count == 0)
        pthread_cond_wait(&not_empty, &mutex);  // Wait until not empty
    int item = buffer[out];
    out = (out + 1) % N;
    count--;
    pthread_cond_signal(&not_full);  // Wake producer
    pthread_mutex_unlock(&mutex);
    return item;
}

5. Common Pitfalls

Pitfall 1: Semaphore ordering in Producer-Consumer

Mistake: wait(mutex) then wait(empty) — if buffer is full, producer holds mutex and blocks. Consumer can't enter (mutex needed). Deadlock. Correct: Always wait on resource semaphore first, then mutex.

Pitfall 2: Forgotten signal/broadcast

Mistake: Thread calls pthread_cond_wait but no other thread ever signals. Result: The thread waits forever (deadlock).

Pitfall 3: Spurious wakeups

Mistake: Using if instead of while for condition check.
c
// WRONG: spurious wakeup breaks this
if (count == 0) pthread_cond_wait(...);
// Correct: always re-check condition
while (count == 0) pthread_cond_wait(...);

6. 📐 Key Formulas / Concepts

ConceptDefinitionUse Case
Race conditionConcurrent access to shared data → unpredictable resultPrevent with synchronization
MutexSimple lock (acquire/release)Mutual exclusion
SpinlockBusy-wait lockShort critical sections
SemaphoreCounter + wait/signalResource pools, signaling
MonitorHigh-level construct with automatic mutex + CVStructured synchronization
Condition variableWait/signal inside monitorComplex condition waiting

7. 📝 Practice Questions

Q1: What is a race condition? Give an example with two threads incrementing a counter.
Answer: When two threads simultaneously execute counter++, the operation (load, increment, store) interleaves. Both read the same initial value, both increment to N+1, and both write N+1 — losing one increment. The fix is to use a mutex lock around the critical section. Q2: Explain the difference between mutex and semaphore.
Answer: A mutex is a binary lock with ownership tracking (only the locking thread can unlock). A semaphore is a signaling mechanism that can be posted by any thread. A binary semaphore can be used like a mutex but without ownership. Counting semaphores manage multiple resources. Q3: In the producer-consumer problem, why must we check the buffer condition with a while loop instead of if?
Answer: To handle spurious wakeupspthread_cond_wait can return even without a signal. Also, after waking, another thread may have changed the condition. The while loop re-checks the condition. Q4: Write pseudocode using semaphores to solve the readers-writers problem (reader preference).
Answer:
c
sem_t resource;  // Binary semaphore for writer exclusion
sem_t rmutex;    // Protect read_count
int read_count = 0;

// Writer
wait(resource);
write();
signal(resource);

// Reader
wait(rmutex);
read_count++;
if (read_count == 1) wait(resource);
signal(rmutex);
read();
wait(rmutex);
read_count--;
if (read_count == 0) signal(resource);
signal(rmutex);
Q5: What is the difference between pthread_cond_signal and pthread_cond_broadcast?
Answer: signal wakes exactly one waiting thread (if any). broadcast wakes all waiting threads. Use signal when only one thread can proceed (e.g., a slot became available). Use broadcast when multiple threads may need to re-check their conditions (e.g., after a state change).

8. 🔗 Cross-References

Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.