OS Chapter 2-1
OS Chapter 2-1
Synchronization
Cooperating Processes
• Background
• The Critical-Section Problem
• Synchronization Hardware
• Semaphores
• Classical Problems of Synchronization
• Critical Regions
• Monitors
• Atomic Transactions
Background
• Concurrent access to shared data may result in
data inconsistency.
• Maintaining data consistency requires
mechanisms to ensure the orderly execution
of cooperating processes.
• Suppose that we modify the producer-
consumer code by adding a variable counter,
initialized to 0 and incremented each time a
new item is added to the buffer.
• The new scheme is illustrated by the following:
Cont’d
• Shared data
typedef .... item;
item buffer[N];
int in, out, counter;
in = 0;
out = 0;
counter = 0;
Cont’d
• Producer process
while(true) {
...
produce an item in nextp
...
while(counter == n)
no-op;
buffer[in] = nextp;
in = (in+1)%n;
counter = counter + 1;
}
Cont’d
• Consumer process
while(true) {
while(counter == 0)
no-op;
nextc = buffer[out];
out = (out+1)%n;
counter = counter - 1;
...
consume the item in nextc
...
}
• The statements:
counter = counter + 1;
counter = counter - 1;
• must be excuted atomically.
The Critical-Section Problem
• Shared data:
bool lock=false;
Process Pi
bool key;
while(true) {
key = true;
do {
Exchg(lock,key);
}while(key);
critical section
lock = false;
remainder section
}
Semaphore - synchronization tool that does
not require busy waiting
• Semaphore S
• integer variable introduced by Dijkstra can only be
accessed via two indivisible (atomic) operations
wait(S): S = S - 1; if S < 0 then block(S)
signal(S): S = S + 1; if S <= 0 then wakeup(S)
• sometimes wait and signal are called down and up or P
and V
• block(S) - results in suspension of the process invoking it
(sometimes called sleep).
• wakeup(S) - results in resumption of exactly one process
that has invoked block(S)
Cont’d
• Example: critical section for n processes
• Shared variables
semaphore mutex=1;
Process Pi
while(true) {
wait(mutex);
critical section
signal(mutex);
remainder section }
• Implementation of the wait and signal operations so that they must execute
atomically.
• Uniprocessor:
– Disable interrupts around the code segment implementing the wait and signal
operations.
• Multiprocessor:
– If no special hardware provided, use a correct software solution to the critical-
section problem, where the critical sections consist of the wait and signal
operations.
– Use special hardware if available, i.e., TestandSet:
Implementation of wait(S) operation with the TestandSet instruction:
• Shared variables
boolean lock = false;
• Code for wait(S)
while (TestandSet(lock));
S = S - 1;
if (S < 0) {
lock = false;
block(S);
} else
lock = false;
• Code for signal(S)
while (TestandSet(lock));
S = S + 1;
if (S <=0)
wakeup(S);
lock = false;
• Race condition exists!
Cont’d
• Better Code for wait(S)
while (TestandSet(lock1));
while (TestandSet(lock));
S = S - 1;
if (S < 0) {
lock = false;
block(S);
} else
lock = false;
lock1 = false;
lock1 serialises the waits.
• Semaphore can be used as general synchronization tool:
• Execute B in Pj only after A executed in Pi
• Use semaphore flag initialized to 0
• Code:
Pi Pj
-- --
. .
. .
. .
A wait(flag)
signal(flag) B
Cont’d
• Consumer process
while(true) {
wait(full); /* wait while no data */
wait(mutex);
...
remove an item from buffer to nextc
...
signal(mutex);
signal(empty); /* one less in buffer */
...
consume the item in nextc
...
}
Readers-Writers Problem
• Shared data
int p[N]; /* status of the philosophers */
semaphore s[N]=0; /* semaphore for each philosopher */
semaphore mutex=1; /* semaphore for mutual exclusion */
• Code
#define LEFT(n) (n+N-1)%N /* Macros to give left */
#define RIGHT(n) (n+1)%N /* and right around the table */
void test(int no) { /* can philosopher 'no' eat */
if ((p[no] == HUNGRY) &&
(p[LEFT(no)] != EATING) &&
(p[RIGHT(no)] != EATING) ) {
p[no]=EATING;
signal(s[no]); /* if so then eat */
}
}
Cont’d
void take_forks(int no) { /* get both forks */
wait(mutex); /* only one at a time here please */
p[no]=HUNGRY; /* I'm Hungry */
test(no); /* can I eat? */
signal(mutex);
wait(s[no]); /* wait until I can */ }
void put_forks(int no) { /* put the forks down */
wait(mutex); /* only one at a time here */
p[no]=THINKING; /* let me think */
test(LEFT(no)); /* see if my neighbours can now eat */
test(RIGHT(no));
signal(mutex); }
void philosopher(int no) {
while(1) {
...think....
take_forks(no); /* get the forks */
....eat.....
put_forks(no); /* put forks down */
}
return NULL;}
High-level synchronization constructs
Monitors
• monitor ProducerConsumer
condition full, empty;
integer count;
procedure enter;
begin
if count = N then wait(full);
...enter item...
count := count + 1;
if count = 1 then signal(empty)
end;
procedure remove;
begin
if count = 0 then wait(empty);
...remove item...
count := count - 1;
if count = N - 1 then signal(full)
end;
count := 0;
end monitor;
Cont’d
• procedure producer;
begin
while true do
begin
...produce item...
ProducerConsumer.enter
end
end;
procedure consumer;
begin
while true do
begin
ProducerConsumer.remove;
...consume item...
end
end;
The dining philosophers problem can also be solved easily
• monitor dining-philosophers
status state[n];
condition self[n];
procedure pickup (i:integer);
begin
state[i] := hungry;
test (i);
if state[i] <> eating then wait(self[i]);
end;
procedure putdown (i:integer);
begin
state[i] := thinking;
test (i+4 mod 5);
test (i+1 mod 5);
end;
Cont’d
• procedure test (k:integer);
begin
if state[k+4 mod 5] <> eating
and state[k] = hungry
and state[k+1 mod 5] <> eating
then begin
state[k] := eating;
signal(self[k]);
end;
end;
begin
for i := 0 to 4
do state[i] := thinking;
end
end monitor
Cont’d
• procedure philosopher(no:integer);
begin
while true do
begin
...think....
pickup(no);
....eat.....
putdown(no)
end
end
• There are very few languages that support constructs such as
monitors... expect this to change. One language that does is Java.
Here is a Java class that can be used to solve the producer
consumer problem.
Cont’d
class CubbyHole {
private int seq;
private boolean available = false;
public synchronized int get() {
while (available == false) {
try {
wait();
} catch (InterruptedException e) {} }
available = false;
notify();
return seq; }
public synchronized void put(int value) {
while (available == true) {
try {
wait();
} catch (InterruptedException e) {} }
seq = value;
available = true;
notify(); } }
Monitor implementation using semaphores