Process Synchronization
Process Synchronization
Process synchronization is the coordination of concurrent processes to ensure correct execution when accessing shared resources.
Why Synchronization?
When multiple processes access shared data concurrently, the final result depends on the order of execution. This leads to:
- Race Conditions - Output depends on execution sequence
- Data Inconsistency - Shared data becomes corrupted
- Unpredictable Results - Different runs produce different outputs
Critical Section Problem
The Critical Section is a code segment where shared resources are accessed.
Structure of a Process
Entry Section ← Request to enter critical section
↓
Critical Section ← Access shared resources
↓
Exit Section ← Release critical section
↓
Remainder Section ← Non-critical codeRequirements for Solution
Any valid solution must satisfy these three conditions:
| Requirement | Description |
|---|---|
| Mutual Exclusion | Only one process can be in critical section at a time |
| Progress | If no process is in CS, a waiting process must be allowed to enter |
| Bounded Waiting | Limit on how long a process waits after requesting entry |
Race Condition
A race condition occurs when multiple processes access and manipulate shared data concurrently, and the outcome depends on the order of execution.
Example:
// Shared variable
int counter = 5;
// Process P1
counter++; // counter = counter + 1
// Process P2
counter--; // counter = counter - 1
// Expected: counter = 5
// Possible results: 4, 5, or 6 (depending on interleaving)Why it happens:
counter++ in machine code:
LOAD counter → R1
ADD R1, 1
STORE R1 → counter
If P1 and P2 interleave incorrectly,
one update can be lost.Peterson's Solution
A classical software-based solution for two processes.
// Shared variables
int turn; // Whose turn it is
bool flag[2]; // Ready to enter CS
// Process Pi (i = 0 or 1, j = 1-i)
flag[i] = true; // I want to enter
turn = j; // Give turn to other
while (flag[j] && turn == j)
; // Wait
// CRITICAL SECTION
flag[i] = false; // Done with CS
// REMAINDER SECTIONProperties:
- Satisfies all three requirements
- Only works for two processes
- Busy waiting (wastes CPU cycles)
- May not work on modern CPUs (memory reordering)
Synchronization Hardware
Test and Set Lock (TSL)
Atomic instruction that tests and modifies a memory word.
// Hardware instruction (atomic)
bool test_and_set(bool *target) {
bool rv = *target;
*target = true;
return rv;
}
// Usage
bool lock = false;
while (test_and_set(&lock))
; // Wait
// CRITICAL SECTION
lock = false;
// REMAINDER SECTIONCompare and Swap (CAS)
// Hardware instruction (atomic)
int compare_and_swap(int *value, int expected, int new_value) {
int temp = *value;
if (*value == expected)
*value = new_value;
return temp;
}Advantages of Hardware Solutions
- Simple to use
- Works for any number of processes
- Can support multiple critical sections
Disadvantages
- Busy waiting still present
- Starvation possible
- Deadlock possible in complex scenarios
Mutex Locks
Mutex (Mutual Exclusion) is the simplest synchronization tool.
// Mutex operations
acquire() {
while (!available)
; // Busy wait
available = false;
}
release() {
available = true;
}
// Usage
acquire(mutex);
// CRITICAL SECTION
release(mutex);Properties:
- Binary lock (locked or unlocked)
- Must be released by the same process that acquired it
- Simple but uses busy waiting (spinlock)
Semaphores
A semaphore is an integer variable accessed through two atomic operations.
Operations
// Wait operation (P or down)
wait(S) {
while (S <= 0)
; // Busy wait
S--;
}
// Signal operation (V or up)
signal(S) {
S++;
}Types of Semaphores
Binary Semaphore (Mutex)
- Value can be 0 or 1
- Used for mutual exclusion
semaphore mutex = 1;
wait(mutex);
// CRITICAL SECTION
signal(mutex);Counting Semaphore
- Value can be any non-negative integer
- Used for resource allocation
// Example: 5 instances of a resource
semaphore resource = 5;
wait(resource); // Acquire one instance
// USE RESOURCE
signal(resource); // Release instanceSemaphore without Busy Waiting
typedef struct {
int value;
struct process *list; // Waiting queue
} semaphore;
wait(semaphore *S) {
S->value--;
if (S->value < 0) {
add this process to S->list;
block();
}
}
signal(semaphore *S) {
S->value++;
if (S->value <= 0) {
remove process P from S->list;
wakeup(P);
}
}Classic Synchronization Problems
1. Producer-Consumer Problem
Problem: Producer produces items, Consumer consumes them. Buffer has limited capacity.
semaphore mutex = 1; // Mutual exclusion
semaphore empty = N; // Empty slots (initially N)
semaphore full = 0; // Filled slots (initially 0)
// Producer
while (true) {
produce_item();
wait(empty); // Wait for empty slot
wait(mutex); // Enter CS
add_to_buffer();
signal(mutex); // Exit CS
signal(full); // Signal item added
}
// Consumer
while (true) {
wait(full); // Wait for item
wait(mutex); // Enter CS
remove_from_buffer();
signal(mutex); // Exit CS
signal(empty); // Signal slot freed
consume_item();
}2. Reader-Writer Problem
Problem: Multiple readers can read simultaneously, but writers need exclusive access.
semaphore mutex = 1; // Protect read_count
semaphore rw_mutex = 1; // Mutual exclusion for writers
int read_count = 0; // Number of readers
// Writer
wait(rw_mutex);
// WRITE
signal(rw_mutex);
// Reader
wait(mutex);
read_count++;
if (read_count == 1)
wait(rw_mutex); // First reader blocks writers
signal(mutex);
// READ
wait(mutex);
read_count--;
if (read_count == 0)
signal(rw_mutex); // Last reader unblocks writers
signal(mutex);Variations:
- First Reader-Writer: Readers have priority
- Second Reader-Writer: Writers have priority
3. Dining Philosophers Problem
Problem: 5 philosophers sit around a table. Each needs two forks to eat. Forks are shared between adjacent philosophers.
P0
F4 F0
P4 P1
F3 F1
P3 P2
F2Naive Solution (Deadlock Prone):
semaphore fork[5] = {1, 1, 1, 1, 1};
// Philosopher i
while (true) {
think();
wait(fork[i]); // Pick left fork
wait(fork[(i+1) % 5]); // Pick right fork
eat();
signal(fork[i]); // Put left fork
signal(fork[(i+1) % 5]); // Put right fork
}
// DEADLOCK if all pick left fork simultaneously!Solutions:
- Allow at most 4 philosophers at table
- Pick both forks atomically
- Odd philosophers pick left first, even pick right first
- Use a waiter (mutex) to coordinate
Monitors
A high-level synchronization construct that encapsulates shared data and operations.
Structure
monitor MonitorName {
// Shared variables
// Condition variables
condition x, y;
// Procedures
procedure P1() { ... }
procedure P2() { ... }
// Initialization code
init() { ... }
}Properties
- Only one process can be active inside monitor at a time
- Automatic mutual exclusion
- Condition variables for waiting
Condition Variables
x.wait(); // Suspend calling process
x.signal(); // Resume one suspended processDifference from Semaphores:
signal()has no effect if no process is waiting- Semaphore
signal()always increments
Producer-Consumer with Monitor
monitor ProducerConsumer {
int buffer[N];
int count = 0;
condition full, empty;
procedure insert(item) {
if (count == N)
full.wait();
buffer[count++] = item;
empty.signal();
}
procedure remove() {
if (count == 0)
empty.wait();
item = buffer[--count];
full.signal();
return item;
}
}Comparison Table
| Mechanism | Level | Busy Waiting | Complexity |
|---|---|---|---|
| Peterson's | Software | Yes | Medium |
| TSL/CAS | Hardware | Yes | Low |
| Mutex | OS | Yes (spinlock) | Low |
| Semaphore | OS | Optional | Medium |
| Monitor | Language | No | High |
Common Interview Questions
Q1: What is a race condition?
A: A race condition occurs when multiple processes access shared data concurrently and the final result depends on the order of execution.
Q2: Difference between mutex and semaphore?
A:
- Mutex is binary (0 or 1), semaphore can be any non-negative integer
- Mutex must be released by the same process that acquired it
- Semaphore is used for signaling, mutex for mutual exclusion
Q3: What is a spinlock?
A: A lock that uses busy waiting. The process continuously checks if the lock is available, wasting CPU cycles but avoiding context switch overhead.
Q4: What is priority inversion?
A: When a high-priority process is blocked waiting for a resource held by a low-priority process, which may be preempted by medium-priority processes.
Q5: How to prevent deadlock in Dining Philosophers?
A:
- Allow max 4 philosophers at table
- Pick both forks atomically
- Odd/even philosophers pick forks in different order
- Use asymmetric solution
Key Takeaways
- Race conditions cause unpredictable behavior with shared data
- Critical section solutions must ensure mutual exclusion, progress, and bounded waiting
- Semaphores are versatile but error-prone
- Monitors provide automatic mutual exclusion
- Classic problems (Producer-Consumer, Readers-Writers, Dining Philosophers) demonstrate synchronization challenges