What happens if both producer and consumer simultaneously do counter + 1 and counter - 1 respectively?
It is a non-deterministic situation called race condition
Race conditions
counter + 1
counter - 1
load r0, counter
load r0, counter
add r0, 1
sub r0, 1
store r0, counter
store r0, counter
Why there are race conditions?
They happend because of the different relative speed between two processes when operating on shared data.
Depending on that speed we obtain different results (not deterministic)
The increase or decrease operation is not atomic
An atomic operation is one that is either fully executed or not executed at all.
We need parts of the code to be executed exclusively: critical sections
Critical section
Code section in which shared variables are updated
The mutual exclusion is a mechanism that ensures that only one process accesses a critical section exclusively.
Critical section requirements
Mutual exclusion must be ensured
Progress must be ensured
Limited waiting time must be guaranteed
How to provide critical sections
Control variables
Specific machine instructions (test_and_set, swap)
Operating system primitives (semaphores)
Solutions provided by third parties (language, external services, etc.)
Control variables (first try)
We assume that load and store machine operations are atomic
Two threads \(T_i\) y \(T_j\) share the turn variable
do {
while (turn != i) do_nothing();
// CRITICAL SECTION
turn = j;
....
} while (TRUE);
mutual exclusion
progress
finite wait
Control variables (second try)
flag[i] = false
flag[j] = false
flag variable signals the intention of a process i to enter the critical section
do {
while (flag[j]) do_nothing();
flag[i] = true;
// CRITICAL SECTION
flag[i] = FALSE;
...
} while (true);
mutual exclusion
progress
finite wait
Control variables (third try)
flag[i] = false
flag[j] = false
flag variable signals the intention of a process i to enter the critical section
do {
flag[i] = true;
while (flag[j]) do_nothing();
// CRITICAL SECTION
flag[i] = FALSE;
...
} while (true);
mutual exclusion
progress
finite wait
Control variables (The Dekker's Algorithm)
flag[i] = false
flag[j] = false
turno = i
do {
flag[i] = true;
turn = j;
while (flag[j] && (turn == j)) do_nothing();
// CRITICAL SECTION
flag[i] = false;
resto de la sección
} while (true);
mutual exclusion
progress
finite wait
How does test_and_set work?
test_and_set(&memory)
pseudocode in C
inttest_and_set(int *memory_position){
int aux;
aux = *dest;
*dest = TRUE;
return (aux);
}
do {
while (test_and_set (&lock)) do_nothing();
// CRITICAL SECTION
lock = false;
...
} while (TRUE);
Critical sections with test_and_set
mutual exclusion
progress
finite wait
easy to extend to many processes
require shared memory
uses busy wait
Semaphores
They are objects managed by the kernel
It is a special type of variable that allows two operations
P(S) Wait until the semaphore is positive, and then decrease it.
V(S) Increase the semaphore by one.
Both operations P and V are atomic
The producer-consumer problem with semaphores
The consumer waits if there is nothing to consume
The producer waits if there is no room left in the buffer
All the operations have to be done in mutual exclusion
smf_full <- 0; Counts the number of elements in the buffer
smf_empty <- N; Counts the number of free positions in the buffer
mutex <- 1; to provide mutual exclusion
Producer code
data = produce()
P(sem_empty)
P(mutex)
push(data)
V(mutex)
V(sem_full)
Consumer code
P(sem_full)
P(mutex)
data = pop()
V(mutex)
V(sem_empty)
Some open questions?
Does the order of operations P and V matter?
What if there were two producers or two consumers?
If one participant had to wait, would it be a busy wait?
What are the implications in CPU schedulling?
Semaphore implementation
P and V are software constructions
Both of them involve and rely on the kernel to:
Avoid race conditions mutual exclusion
Avoid busy-waiting synchronization and state of a process
V (Semaphore sem) {
CLI; //Disable interruptsif (is_empty(sem.q) {
sem.counter ++;
} else {
sem.q.pop(calling_process);
wake_up(calling_process);
}
STI; //Enable interrupts
call_scheduler(); // Depending on the scheduling policy
}
Synchronization examples
The producer and consumers problem.
The sleeping barber problem.
The Readers–writers problem.
The dining philosophers problem.
Andrew S. Tanenbaum (2001). Modern Operating Systems (PDF) (2nd ed.). Upper Saddle River, NJ: Pearson. p. 129. ISBN 9780130313584
The sleeping barber problem
A barbershop with one barber, one barber chair, and a waiting room with n chairs for waiting customers.
If no customers, the barber falls asleep in the chair.
A customer must wake the barber if he is asleep.
If a customer arrives while the barber is working, the customer leaves if all chairs are occupied otherwise sits and wait.
When the barber finishes a haircut, he inspects the waiting room to see if there are any waiting customers and falls asleep if there are none
Wikipedia contributors. "Sleeping barber problem." Wikipedia, The Free Encyclopedia. Wikipedia, The Free Encyclopedia, 20 Aug. 2024. Web. 11 Nov. 2024.
The barber
do {
P(clients);
P(mutex);
// Critical region
clients_waiting --;
V(barbers);
V(mutex);
// Cut the hair
} while (true);
The client
do {
P(mutex);
if (clients_waiting < CHAIR_COUNT) {
clients_waiting ++;
V(clients);
V(mutex);
P(barbers);
// Cut the hair
} else {
V(mutex);
// Leave, as there are no chairs
}
} while (true);
The readers-writer problem
Writers only enter while no other writer or reader is in the DB. Writers have priority over readers.
Readers only enter if no writer is accessing or waiting.
P(mutex)
if (WA+WW ==0) {
// No writers accessing or waiting
RA++;
V(readers);
} else {
RW++;
}
V(mutex);
P(readers);
// Access the DB to read
P(mutex);
RA--;
if (RA==0&& WW>0) {
// No more readers and writers waiting// Unlock one writer
V(writers)
WW--;WA++;
}
V(mutex);
Writers
P(mutex)
if (WA+RA+WW ==0) {
// No writers accessing or waiting// No readers accessing
V(writers);
WA++;
} else {
WW++;
}
V(mutex);
P(writers);
// Access the DB to write data
P(mutex);
WA--;
if (WW>0){
// There are writers waiting
V(writers);
WA++; WW--;
} else {
while (RW>0) {
V(readers);
RA++; RW--;
}
}
V(mutex)