Operating Systems - Process Synchronization
The Critical Section Problem
Requirements
- Mutual exclusion: if process Pi is executing in its critical section, then no other processes can be executing in their critical sections.
- Progress: if no process is executing in the critical section, then one process will be eventually selected.
- Bounded Waiting: a process can eventually get the chance to enter critical section.
Solution Template
while(1){
/*entry section*/
...
/*critical section*/
...
/*exit section*/
...
/*remainder section*/
}
Failed Algorithm #1
Initialization
/*shared between two processes*/
int turn = 0;
Implementation for Process Pi
while(1){
while(turn != i);
/*critical section*/
...
turn = j;
/*remainder section*/
}
“Progress” Violation Example
P0 | P1 |
---|---|
while(turn != 0); | —- |
—- | while(turn != 1); |
/*critical section*/ | —- |
turn = 1; | —- |
—- | /*critical section*/ |
—- | turn = 0; |
—- | while(turn != 1); looping forever |
Failed Algirithm #2
Initialization
/*shared between two processes*/
bool flag[2];
flag[0] = flag[1] = false;
Implementation for Process Pi
while(1){
flag[i] = true;
while(flag[j]);
/*critical section*/
...
flag[i] = false;
/*remainder section*/
}
“Progress” Violation Example
P0 | P1 |
---|---|
flag[0] = true; | —- |
—- | flag[1] = true; |
—- | while(flag[0]); looping |
while(flag[1]); looping forever | —- |
Solution without OS Support
Initialization
/*shared between two processes*/
int turn = 0;
bool flag[2];
flag[0] = flag[1] = false;
Implementation for Process Pi
while(1){
flag[i] = true;
turn = j;
while(flag[j] && (turn == j));
/*critical section*/
...
flag[i] = false;
/*remainder section*/
}
Solution with Synchronization Hardware
Atomic Hardware Instruction testAndSet()
/*atomically set a boolean to be true, and return the original value*/
boolean testAndSet (bool *target) {
/*no context switch from here*/
bool rv = *target;
*target = true;
return rv;
/*allow context switch from here*/
}
Initialization
/*shared between two processes*/
boolean lock = false;
Implementation for Process Pi
while(1){
while(testAndSet(&lock));
/*critical section*/
...
lock = false;
/*remainder section*/
}
Semaphore
Traditional Implementation - Busy Waiting
class Sempahore{
private:
int value;
public:
void wait();
void signal();
}
/*atomic*/
void Semaphore::wait(){
while(value <= 0);
value--;
}
/*atomic*/
void Semaphore::signal(){
value++;
}
Modern Implementation - Blocking
class Sempahore{
private:
int value;
Queue<Process> q;
public:
void wait();
void signal();
}
/*atomic*/
void Semaphore::wait(){
value--;
if(value < 0){
block(); //system call to block the current process
}
}
/*atomic*/
void Semaphore::signal(){
value++;
if(value <= 0){
wakeup(q.dequeue()); //system call to wake up a process
}
}
Solution with Semaphore
Initialization
/*shared between two processes*/
Semaphore *mutex = new Semaphore(1);
Implementation for Process Pi
while(1){
mutex->wait();
/*critical section*/
...
mutex->signal();
/*remainder section*/
}
Solutions of Classic Synchronization Problems using Semaphores
Bounded Buffer (aka. Producer/Consumer)
Requirements
- Two proceses, the producer and the consumer, share a same buffer with finite capacity.
- The producer generates a piece of data, add it to the buffer, and start again.
- At the same time, the consumer removes data from the buffer one piece at a time.
- The producer won’t try to add data into the buffer if it’s full.
- The consumer won’t try to remove data from an empty buffer.
Initialization
#DEFINE CAPACITY 10;
Buffer* buffer = new Buffer(CAPACITY);
Semaphore* empty = new Semaphore(CAPACITY);
Semaphore* full = new Semaphore(0);
Semaphore* mutex = new Semaphore(1);
Producer Process
while(1){
Item* item = produce();
empty->wait();
mutex->wait();
buffer->add(item);
mutex->signal();
full->signal();
}
Consumer Process
while(1){
full->wait();
mutex->wait();
consume(buffer->remove());
mutex->signal();
empty->signal();
}
Readers/Writers
Requirements
- Multiple reader and writer processes.
- Readers can read concurrently.
- Writing is performed ASAP (i.e. writers have precedence over readers).
Initialization
Essay* essay = new Essay();
int readcount = 0;
Semaphore* resource = new Semaphore(1);
Semaphore* mutex = new Semaphore(1);
Writer Process
resource->wait();
essay->write();
resource->signal();
Reader Process
mutex->wait();
readcount++;
if(readcount == 1){
resource->wait();
}
mutex->signal();
essay->read();
mutex->wait();
readcount--;
if(readcount == 0){
resource->signal();
}
mutex->signal();
Dining Philosophers
Requirements
- Five silent philosophers sit at a round table with bowls of spaghetti.
- Forks are placed between each pair of adjacent philosophers.
- Each philosopher must alternately think and eat.
- A philosopher can only eat spaghetti when he has both left and right forks.
- Each fork can be held by only one philosopher and so a philosopher can use the fork only if it is not being used by another philosopher.
- After he finishes eating, he needs to put down both forks so they become available to others.
- A philosopher can take the fork on his right or the one on his left as they become available, but cannot start eating before getting both of them.
- Eating is not limited by the remaining amounts of spaghetti or stomach space.
Initializaiton
#DEFINE CAPACITY 5;
Semaphore* forks[CAPACITY];
for(int i = 0; i < CAPACITY; i++){
forks[i] = new Semaphore(1);
}
Process for Each Philosopher $i$
while(1){
fork[i]->wait();
fork[(i + 1) % CAPACITY]->wait();
eat();
fork[i]->signal();
fork[(i + 1) % CAPACITY]->signal();
think();
}
Notes
This solution can suffer from deadlock (e.g. all philosophers decide to eat at the same time and all pick up
their left chopstick first) and/or starvation. Some ways to avoid deadlock are:
- Don’t allow all philosophers to sit and eat/think at once.
- Pick up both chopsticks in a critical section
- Alternate choice of first chopstick
More Examples on Synchronization Algorithms
Example #1 - Failed Algorithm (from Tutorial 4 Question 2)
Initializaiton
int flag[2];
flag[0] = flag[1] = 0;
Algorithm for Pi
while(1){
while(flag[j] == 1);
flag[i] = 1;
/*critical section*/
...
flag[i] = 0;
/*remainder section*/
}
“Mutual Exclusion” Violation Example
P0 | P1 |
---|---|
while(flag[1] == 1) | —- |
—- | while(flag[0] == 1) |
flag[0] = 1 | —- |
/*critical section*/ already in critical section | —- |
—- | flag[1] = 1 |
—- | /*critical section*/ mutual exclusion violated |
Example #2 - Failed Algorithm (from AY14-15 Sem 1 Final Exam)
Initialization
int turn = 0;
bool flag[2];
flag[0] = flag[1] = false;
Algorithm Setting for P0
while(1){
flag[0] = true;
turn = 1;
while(flag[1] && turn == 1);
/*critical section*/
...
flag[0] = false;
/*remander section*/
}
Algorithm Setting for P1
while(1){
flag[1] = true;
turn = 0;
while(flag[0]);
/*critical section*/
...
flag[1] = false;
/*remander section*/
}
“Progress” Violation Example
P0 | P1 |
---|---|
flag[0] = true; | —- |
—- | flag[1] = true |
—- | turn = 0; |
turn = 1; | —- |
—- | while(flag[0]) looping |
while(flag[1] && turn == 1) looping forever | —- |
Example #3 - Working Algorithm (from AY13-14 Sem 2 Final Exam)
Atomic Instruction compareAndSwap()
/* if an integer equals to oldV,
* atomically set it to newV,
* and return the original value*/
int compareAndSwap(int* addr, int oldV, int newV){
/*no context switch from here*/
int original = *addr;
if(oldV == original){
*addr = newV;
}
return original;
/*context switch allowed from here*/
}
Implementation for Process Pi
int mutex = 0;
while(1){
while(compareAndSwap(&mutex, 0, 1) == 1);
/*critical section*/
...
mutex = 0;
/*remainder section*/
}
Analysis
This is solution is similar to the testAndSet()
introduced in the lecture notes. Consider false
value as 0 and true
value as 1.