Synchronization and Mutual Exclusion
Basic Concepts
The Critical Section Problem
Critical section: the segment of code that accesses shared resources; only one process may execute it at a time.
Characteristics of a critical section:
- Mutual exclusion: only one process may be inside the critical section at a time
- Bounded waiting: the time a process waits to enter the critical section is finite
- Progress: when no process is in the critical section, a process that requests entry must be able to enter
Structure of a critical section:
do {
entry_section(); // entry section
critical_section(); // critical section
exit_section(); // exit section
remainder_section(); // remainder section
} while (true);
Mutual Exclusion and Synchronization
Mutual exclusion: several processes must not access a shared resource at the same time.
Synchronization: several processes execute in a given order so that operations stay correct.
The difference:
- Mutual exclusion is about “not at the same time”
- Synchronization is about “executing in order”
Software Approaches
1. Single Flag
Algorithm:
- Keep a single flag variable
turn - Only the process that
turnpoints at may enter the critical section
Implementation:
int turn = 0; // initialized to process 0
// Process 0
while (turn != 0); // wait
critical_section();
turn = 1;
// Process 1
while (turn != 1); // wait
critical_section();
turn = 0;
Problem: entry is forced to alternate, which violates the “progress” requirement.
2. Double Flag
Algorithm:
- Each process keeps its own flag
- Before entering the critical section, a process checks the flag of the other process
Implementation:
bool flag[2] = {false, false};
// Process i
flag[i] = true;
while (flag[j]); // wait for the other process
critical_section();
flag[i] = false;
Problem: two processes may enter the critical section at the same time, which violates “mutual exclusion”.
3. Improved Double Flag
Algorithm:
- Raise your own flag first, then check the other process’s flag
- On a conflict, clear your own flag and try again
Implementation:
bool flag[2] = {false, false};
// Process i
flag[i] = true;
while (flag[j]) {
flag[i] = false;
delay();
flag[i] = true;
}
critical_section();
flag[i] = false;
Problem: a process may wait forever, which violates the “bounded waiting” requirement.
4. Peterson’s Algorithm
Algorithm:
- Combines the
turnflag with theflagflags - Satisfies all three requirements of the critical section problem
Implementation:
bool flag[2] = {false, false};
int turn = 0;
// Process i
flag[i] = true;
turn = j;
while (flag[j] && turn == j);
critical_section();
flag[i] = false;
Characteristics:
- Satisfies mutual exclusion, bounded waiting, and progress
- Written for two processes
- Can be extended to more processes
Hardware Approaches
1. Disabling Interrupts
Principle: disable interrupts before entering the critical section and restore them on exit.
Implementation:
// Enter the critical section
disable_interrupts();
critical_section();
// Leave the critical section
enable_interrupts();
Characteristics:
- Simple and effective
- Works only on a single processor
- Hurts the responsiveness of the system
2. The Test-and-Set Instruction
Principle: atomically test and set the value of a memory location.
Implementation:
bool TestAndSet(bool *lock) {
bool old = *lock;
*lock = true;
return old;
}
bool lock = false;
// Process
while (TestAndSet(&lock));
critical_section();
lock = false;
Characteristics:
- Supported by hardware, therefore efficient
- Works on multiprocessors
- May busy-wait
3. The Compare-and-Swap Instruction
Principle: atomically compare and swap the value of a memory location.
Implementation:
int CompareAndSwap(int *value, int expected, int new_value) {
int temp = *value;
if (*value == expected)
*value = new_value;
return temp;
}
int lock = 0;
// Process
while (CompareAndSwap(&lock, 0, 1) != 0);
critical_section();
lock = 0;
Characteristics:
- Supported by hardware, therefore efficient
- Works on multiprocessors
- More flexible than Test-and-Set
Semaphores
Basic Concepts
Semaphore: an integer variable that, apart from initialization, may only be accessed through two atomic operations:
- P operation (wait): request a resource
- V operation (signal): release a resource
Types of semaphore:
- Binary semaphore: the value is 0 or 1; used for mutual exclusion
- Counting semaphore: the value may be any non-negative integer; used for synchronization
Semaphore Implementation
Structure definition:
typedef struct {
int value;
struct process *list;
} semaphore;
Implementation of the P operation:
void P(semaphore *S) {
S->value--;
if (S->value < 0) {
add_process_to_list(S->list);
block();
}
}
Implementation of the V operation:
void V(semaphore *S) {
S->value++;
if (S->value <= 0) {
remove_process_from_list(S->list);
wakeup();
}
}
Applications of Semaphores
1. Mutual exclusion:
semaphore mutex = 1;
// Process
P(&mutex);
critical_section();
V(&mutex);
2. Synchronization:
semaphore sync = 0;
// Process A
statement_a();
V(&sync);
// Process B
P(&sync);
statement_b();
3. Resource management:
semaphore resource = N; // N resources
// Process
P(&resource);
use_resource();
V(&resource);
Monitors
Basic Concepts
Monitor: a high-level synchronization mechanism that encapsulates shared variables together with the operations on them in one module.
Characteristics of a monitor:
- Encapsulation: shared variables can only be accessed through the procedures inside the monitor
- Mutual exclusion: only one process may execute inside a monitor at a time
- Condition variables: used to synchronize processes with one another
Structure of a Monitor
monitor MonitorName {
// shared variable declarations
shared variables;
// condition variables
condition x, y;
// procedure definitions
procedure P1() {
// procedure body
};
procedure P2() {
// procedure body
};
// initialization code
{
// initialization code
}
}
Condition Variables
Operations:
wait(c): the process waits on condition variablecsignal(c): a process waiting on condition variablecis woken up
Implementation:
typedef struct {
int value;
struct process *list;
} condition;
void wait(condition *c) {
c->value++;
block();
}
void signal(condition *c) {
if (c->value > 0) {
c->value--;
wakeup();
}
}
Classical Synchronization Problems
1. The Producer-Consumer Problem
Problem:
- Producer processes produce data and put it into a buffer
- Consumer processes take data out of the buffer
- The buffer is finite, so the two sides must be synchronized
Solution:
#define N 100
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;
// Producer
void producer() {
while (true) {
item = produce_item();
P(&empty);
P(&mutex);
insert_item(item);
V(&mutex);
V(&full);
}
}
// Consumer
void consumer() {
while (true) {
P(&full);
P(&mutex);
item = remove_item();
V(&mutex);
V(&empty);
consume_item(item);
}
}
2. The Readers-Writers Problem
Problem:
- Several readers may read the shared data at the same time
- A writer must have exclusive access
- Readers and writers may not access the data at the same time
Reader-preference solution:
semaphore mutex = 1;
semaphore wrt = 1;
int readcount = 0;
// Reader
void reader() {
P(&mutex);
readcount++;
if (readcount == 1)
P(&wrt);
V(&mutex);
read_data();
P(&mutex);
readcount--;
if (readcount == 0)
V(&wrt);
V(&mutex);
}
// Writer
void writer() {
P(&wrt);
write_data();
V(&wrt);
}
3. The Dining Philosophers Problem
Problem:
- 5 philosophers sit around a round table
- Each philosopher needs two chopsticks to eat
- The chopsticks lie between the philosophers
Solution:
semaphore chopstick[5] = {1, 1, 1, 1, 1};
semaphore mutex = 1;
void philosopher(int i) {
while (true) {
think();
P(&mutex);
P(&chopstick[i]);
P(&chopstick[(i+1)%5]);
V(&mutex);
eat();
V(&chopstick[i]);
V(&chopstick[(i+1)%5]);
}
}
The Deadlock Problem
Definition of Deadlock
Deadlock: a situation in which several processes wait for one another because they compete for resources.
Deadlock Conditions
The four necessary conditions:
- Mutual exclusion: a resource cannot be used by several processes at the same time
- Hold and wait: a process does not release the resources it holds while waiting for other resources
- No preemption: a resource cannot be forcibly taken away
- Circular wait: a chain of waiting processes forms a cycle
Deadlock Prevention
Breaking mutual exclusion:
- Use shareable resources
- Use virtualization
Breaking hold and wait:
- Allocate all resources at once
- Use a resource pre-allocation policy
Breaking no preemption:
- Allow resources to be preempted
- Assign priorities to resources
Breaking circular wait:
- Allocate resources in a fixed order
- Use a resource numbering scheme
Deadlock Avoidance
The Banker’s algorithm:
- Check whether an allocation is safe before granting it
- Grant resources only in a safe state
Safety check:
bool is_safe_state() {
int work[RESOURCES];
bool finish[PROCESSES];
// initialization
for (int i = 0; i < RESOURCES; i++)
work[i] = available[i];
for (int i = 0; i < PROCESSES; i++)
finish[i] = false;
// look for a process that can finish
for (int count = 0; count < PROCESSES; count++) {
bool found = false;
for (int i = 0; i < PROCESSES; i++) {
if (!finish[i] && can_allocate(i, work)) {
for (int j = 0; j < RESOURCES; j++)
work[j] += allocation[i][j];
finish[i] = true;
found = true;
}
}
if (!found) return false;
}
return true;
}
Deadlock Detection
Resource allocation graph:
- Nodes represent processes and resources
- Edges represent allocation and request relationships
- Look for a cycle in the graph
Detection algorithm:
bool detect_deadlock() {
// simplify the resource allocation graph
while (a_simplifiable_node_exists()) {
simplify_that_node();
}
// check whether any edges remain
if (graph_has_remaining_edges())
return true; // a deadlock exists
else
return false; // no deadlock exists
}
Deadlock Recovery
Process termination:
- Terminate all deadlocked processes
- Terminate deadlocked processes one at a time
Resource preemption:
- Choose a victim process
- Roll back to a safe state
- Reallocate the resources
| Term | Abbreviation | Explanation |
|---|---|---|
| Synchronization | - | Coordinating the order in which processes execute |
| Mutual Exclusion | - | Exclusive access to a critical resource |
| Critical Resource | - | A resource that only one process may access at a time |
| Critical Section | - | The code segment that accesses a critical resource |
| Race Condition | - | A situation whose result depends on the order of execution |
| Semaphore | - | A mechanism for synchronization and mutual exclusion |
| Integer Semaphore | - | A semaphore whose value is an integer |
| Record Semaphore | - | A semaphore that carries a waiting queue |
| P Operation | - | The semaphore decrement operation (wait) |
| V Operation | - | The semaphore increment operation (signal) |
| Monitor | - | A high-level synchronization mechanism |
| Condition Variable | - | A waiting condition inside a monitor |
| Mutex | - | A lock for mutually exclusive access |
| Spinlock | - | A lock that is waited for in a busy loop |
| Read-Write Lock | - | A lock that separates readers from writers |
| Producer-Consumer | - | A classical synchronization problem |
| Readers-Writers | - | A classical synchronization problem |
| Dining Philosophers | - | A classical synchronization problem |
| Sleeping Barber | - | A classical synchronization problem |
| Deadlock | - | Processes waiting for one another and unable to make progress |
| Livelock | - | Processes that keep changing state but never make progress |
| Starvation | - | A process that is denied a resource for a long time |
| Barrier | - | A synchronization point where all processes must arrive |
