Cosc 4740
Chapter 6, Part 2
Process Synchronization
Busy loop/waiting problem
• Wastes CPU time
• If scheduler takes into account priority, then
low priority process (becomes CPU bound),
while other may be high priority
– If low priority gets the lock, while the high
priority process will have to wait longer while
low priority process finishes/release lock.
Busy loop fix
• Can use sleep() and wakeup()
• sleep() blocks the process
– it won’t be scheduled
• wakeup() moves the sleeping process to the ready
list.
– If it is blocked for other reasons don’t wake it.
– The wakeup() call is issued by another process. The
sleeper can not wakeup itself.
• Can be setup for a timed event, ie sleep for 1 second, but O/S
“Wakes” the process after 1 second.
Producer/Consumer or Bounded
buffer problem
• 2 processes communication by sharing a fix sized
buffer.
– (limited way): The producer puts stuff in and the
consumer takes stuff out.
– Producer  buffer  consumer
• It can not just put it in or remove it randomly. The
buffer can be full or empty. Must be a balance
between producer and consumer.
First attempt
Buffer: //class
An array
next: //index variable – next input location
// –producer interested in this
last: // next output location
//– consumer interested in this
N: //size-fixed for buffer.
Producer()
while (true) {
produce (item)
put_info( buffer[next], item)
next = (next+1) mod N
}
• This is a simple minded
version. Assumes never full,
never empty, no problems
Consumer()
while (true) {
get_item(buffer[last], item)
last = (last +1) mod n
consume(item)
}
• Problems:
• what if buffer is full or empty?
– If full – don’t put_info
– if empty – don’t get_item.
• One solution is a busy waiting loop while
empty or full.
• OR use sleep() and wakeup()
•
int count = 0 // global –shared
between the processes = # items in
buffer array
producer ()
while (true) {
produce (item)
if (count == N) sleep();
enter_item(item)
count++
if (count == 1)
wakeup(consumer)
}
Consumer ()
while (true) {
if (count == 0) sleep()
remove_item(item)
count --;
if (count == N-1)
wakeup(producer)
consume(item)
}
• problems:
– Consumer codes running reads count and it is zero.
Before it goes to sleep, the process is interrupted.
– The producer process starts running
• puts item in buffer, count = 1 and wakeup consumer.
• Which would do nothing, since it was interrupted and already
on the ready-queue.
– Consumer switched onto the CPU and then goes to
sleep.
– Producer runs until buffer is full and it too goes to
sleep.
The Fix
• In 1965 Dykstra invented semaphores to
solve these problems.
Quick Review
• tsl instruction ()
– it’s an atomic instruction
– allows programmer to set a lock without interruption
• problem: busy/waiting wastes CPU
• solution: sleep & wakeup
• problem: w/ sleep & wakeup – no count of how
many proc are sleeping so could put all to sleep
Semaphore
• Synchronization tool that does not require
busy waiting
• Semaphore S – integer variable
– contains non-neg integers (>= 0)
– Provides a way to count the number of
sleep/wakeups invoked
• Two standard operations modify S: wait()
and signal()
– Originally called P() and V()
Semaphore as General
Synchronization Tool
• Counting semaphore
– integer value can range over an unrestricted domain
• Binary semaphore
– integer value can range only between 0 and 1; can be simpler to
implement
– Also known as mutex locks
• Can implement a counting semaphore S as a binary semaphore
• Provides mutual exclusion
Semaphore S;
//
wait (S);
Critical Section
signal (S);
initialized to 1
Semaphore Implementation
• Must guarantee that no two processes can
execute wait() and signal() on the same
semaphore at the same time
– Wait() and Signal() are also critical sections
for the variables S.
– Note that applications may spend lots of
time in critical sections and therefore busy
waiting is not a good solution.
Semaphore Implementation (2)
• With each semaphore there is an associated
waiting queue. Each entry in a waiting queue has
two data items:
– value (of type integer)
– pointer to next record in the list
• Two operations:
– block – place the process invoking the operation on the
appropriate waiting queue.
– wakeup – remove one of processes in the waiting queue
and place it in the ready queue.
Semaphore Implementation (3)
•
Implementation of wait:
wait (S){
•
Implementation of signal:
Signal (S){
S->value--;
value++;
if (value < 0) {
//add this process to
//waiting queue for S
block();
}
if (value <= 0) {
//remove a process P
//from the waiting queue
//for S
wakeup(P);
}
}
}
Deadlock and Starvation
• Deadlock – two or more processes are waiting indefinitely for an
event that can be caused by only one of the waiting processes
• Let S and Q be two semaphores initialized to 1
P0
P1
wait (S);
wait (Q);
.
.
.
signal (S);
signal (Q);
wait (Q);
wait (S);
.
.
.
signal (Q);
signal (S);
• Starvation – indefinite blocking. A process may never be removed
from the semaphore queue in which it is suspended.
• Priority Inversion – Scheduling problem when lower-priority process
holds a lock needed by higher-priority process
– Solved via priority-inheritance protocol
Classical Problems
• Solutions using Semaphores
– Bounded-Buffer Problem
• Producer-Consumer Problem
– Dining-Philosophers Problem
– Readers and Writers Problem
Bounded-Buffer Problem
•
•
•
•
N buffers, each can hold one item
Semaphore mutex initialized to the value 1
Semaphore full initialized to the value 0
Semaphore empty initialized to the value N.
Bounded Buffer Problem (Cont.)
• The structure of the producer process
while (true) {
// produce an item
produce(item);
wait (empty); //check if buffer is full, if yes, block
wait (mutex); //enter CR to add item.
// add the item to the buffer
signal (mutex); //leave CR system.
signal (full); //increment full
}
Bounded Buffer Problem (2)
• The structure of the consumer process
while (true) {
wait (full); //check to see if buffer is “full”
//start w/full =0, so it blocks if there is nothing yet.
wait (mutex); //Enter CR
// remove an item from buffer
signal (mutex); //leave CR
signal (empty); //increment empty
// consume the removed item
}
Bounded Buffer Problem (3)
• The producer block should be removed
when at least 1 item is removed
• The consumer must tell the producer by a
signal(s) operation.
• Recall that semaphores keep a count of
wait() & signal() calls and are atomic
functions.
Dining-Philosophers Problem
• 5 philosophers
• 5 forks
• Each philosopher
– (1) eat
– (2) thinks
• Catch: If a philosopher wants to each it needs two
forks
philosopher(i)
while(1) {
think()
pick up 2 forks
eat()
put down forks
}
• Problem:
– To eat, a philosopher
needs 2 forks
– 2 neighbors can not eat
at the same time.
– can not pick up more
than 1 fork at a time.
– So 1 philosopher might
starve!
With No locking.
philosopher(i)
left = I; right = (I+1) mod 5;
while(1) {
think()
pick_fork[right] =1
pick_fork [left ] =1
eat ()
put_fork [right]=0
put_fork [ left] =0
}
With Semaphores
semaphore S[5] = {1,1,1,1,1} // init to 1
while (1) {
think();
Wait(S[left]);
Wait(S[right]);
eat ()
Signal(s[left]);
Signal(s[right]);
}
• Now when a fork is not avail, the
philosopher will block.
• Problem:
– If all philosophers pick up left fork 1st, then no
right forks are available.
• We need to be able to pick up both forks or
none, so we need a semaphore to do this:
make picking up forks atomic.
Philosphers(i) {
while(1) {
think();
take_forks(i);
eat();
put_forks(i);
} }
put_forks(i) {
Wait(mutex);
Signal(S[left]);
Signal(S(right]);
Signal(mutex);
}
take_forks(i) {
Wait(mutex);
Wait(S[left]);
Wait(S[right]);
Signal(mutex);
}
So what problems arise?
• So what problems arise?
– If philosophers is blocked on a right or left
fork, he will be deadlocked, because no other
process can put_forks, since it is in it’s critical
section!
• There are other methods that can fix this
problem.
Readers/writers problem
Two types of processes
1. readers – reads information from the
database
2. writers – write to the database
•
And we want concurrent access to the
database.
• problem 1
– 2 writers try to modify the db at same time.
– constraint: At must 1 writer should access the database
at any time
• problem 2
– a reader is attempting to read an entry while it is in the
process of being modified and could read an
intermediate value (or wrong value).
– Constraint: A writer and reader cannot access the
database at the same time.
try #1
semaphore db = 1;
writer () {
P(db); // protect db from other writers and readers
modify database
V(db);
}
readers ()
P(db);
read database
V(db);
}
• no problem with constraints but it excludes other readers
try #2
• we want to allow multiple readers, but no readers
w/ writers
v = 0;
db =1;
writer()
reader()
P(db)
if (v==1)
v = 1;
read (database)
modify(database)
v = 0;
V(db)
• Problem: v is not protected. When you access v, it
should be w/in a semaphore
• Why?
• Also readers can already be in the database, when
the writer enters. Writer is not blocked by readers.
• P(db) in reader, will also block readers too.
• We want a P(db) block on writers put on the by
the 1st reader only and removed by last reader!
Try #3
semaphore mutex
int rc =0; //reader count
reader()
P(mutex) // Protect rc
if (rc ==0) { P(db); }
rc ++;
V(mutex);
read database;
P(mutex)
rc –
if (rc ==0) { V(db); }
V(mutex);
writer()
P(db);
modify database
V(db);
• This solution allows multi readers
– no writer/reader combo, but writer will starve
• may never get a chance if readers keep arriving. Writer
unblocked only when all readers done.
• So what is a fair solution?
– We need to alternate between writers and readers.
– You must maintain a count of # of readers reading and
you must maintain a flag when writer arrives.
– Can be done with higher level primitives to make it
easier.
Problems with Semaphores
• Incorrect use of semaphore operations:
– signal (mutex) …. wait (mutex)
– wait (mutex) … wait (mutex)
– Omitting of wait (mutex) or signal
(mutex) (or both)
• Deadlock and starvation
higher level primitives
• There are two primitives that well look at:
• Conditional Critical Region (CCR)
• Monitor
Conditional Critical Region
• Explicitly designates a section of the
program as critical.
– a mechanism to specify variables to be
protected in critical region.
– conditions under which a critical section may
be entered
designated critical section  region //always assoc w/ resource
variable protection  resource
resource R1name: v1, v2, … Vn
R2name: v1, v2, …, Vn
region R1name when B do S end
• // B: condition of entry: boolean expression
• // S: statement list
• // region statements are scattered through the program. Region
statements can be nested, but CAUTION: deadlocks become very
likely.
Example
• Unisex bathroom problem
– single bathroom
– 2 processes types: male and female
• constraints
– No male & Female in BR at same time, multimale ok, multi-female ok.
resource bathroom: fc=0, mc=0; //protected variables
(male count = mc, etc)
• //Note male and female solution is the same, expect switch mc
and fc
male()
region bathroom when fc = 0 do
mc ++;
use bathroom
mc --;
end
• Problem
– The region statement at any one time is only for
only one process to be in the region, so multimale (multi-female) constraints fails
Try #2
males()
region bathroom when fc==0 do
mc++
end
use bathroom
region bathroom when true do
mc-end
• Allows concurrency. Second part, protect,
so only one process accessing mc at the
same time (mutual exclusion).
• problem: starvation
Dinning Philosophers (again)
resource eat: forks[5] ={0,0,0,0,0}
philosopher(i)
//assume left and right are defined
think();
region eat when ((fork[left] ==0)&&(fork[right]==0) do
fork[left] =1; fork[right]=1;
end
eating();
region eat when true do
fork[left] =0; fork[right]=0;
end
Monitors
• A monitor is a collection of procedures and variables and
data structures
– Processes can access these variables only by invoking the
procedures in the monitor
– At most 1 process can be active at any time in the monitor. This
provides mutual exclusion
– Unlike CCR’s the code is NOT scattered. It is in the monitor
• condition variables provided: 2 operations defined
– wait(c): causes the process to block on C – and the processes
leaves the monitor
– signal(c): unblocks one of the processes. The unblocked process
runs immediately.
Monitors
• Only one process may be active within the monitor at a time
monitor monitor-name
{
// shared variable declarations
procedure P1 (…) { …. }
…
procedure Pn (…) {……}
Initialization code ( ….) { … }
…
}
}
Schematic view of a Monitor
Condition Variables
• condition x, y;
• Two operations on a condition
variable:
– x.wait ()
• a process that invokes the operation is
suspended.
– x.signal ()
• resumes one of processes (if any) that
invoked x.wait ()
Monitor with Condition
Variables
Condition Variables Choices
• If process P invokes x.signal (), with Q in x.wait () state, what should
happen next?
– If Q is resumed, then P must wait
• Options include
– Signal and wait – P waits until Q leaves monitor or waits for another
condition
– Signal and continue – Q waits until P leaves the monitor or waits for
another condition
– Both have pros and cons – language implementer can decide
– Monitors implemented in Concurrent Pascal compromise
• P executing signal immediately leaves the monitor, Q is resumed
– Implemented in other languages including Mesa, C#, Java
Monitor m_name
variable1, …
procedure P1( …)
statements
end
procedure P2(…)
statements
end
begin
// initialization code for variables
end
end m_name
// invoked by: m_name.P1(…)
Unisex (again)
• Bathroom problem again but cure starvation
by alternate the sexes if op. sex is waiting
• int mc, fc, mcw, fcw
– // mcw,fcw male/female count waiting
• condition fq, mq
– // mq/fq male/female queue of processes blocked
procedure enter_bathroom_f( )
if (mc == 0 && mcw == 0)
fc ++
else
fcw ++
// waiting female count
wait(fq)
// blocks females
fcw –
// not waiting any more
fc ++
// now female is in the bathroom
signal(fq)
// signal next female, if none,
// signal is lost, but ok.
procedure exit_bathroom_m( )
// like reader/writer the last male leaving must
// signal to female that he has left.
mc -// male left
if (mc == 0 && fcw !=0)
// last male out and female waiting
// signal wakes up just one process.
signal (fq)
// no other process can interrupt this since the monitor gives exclusive use
// to each procedure as each fq is executed, it runs immediately
• So 2 queues are waiting to enter monitor
• If only male signal process executes the signal(fq),
then processes gets stuck on the queue and
everything starts to block
– deadlock, so we need to have female signaled to signal
the next female to enter.
– Also, since signal is last statement, male does not block
it is finished.
• Each condition has its own queue
Solution to Dining Philosophers
monitor DP {
enum { THINKING; HUNGRY, EATING) state [5] ;
condition self [5];
void pickup (int i) {
state[i] = HUNGRY;
test(i);
if (state[i] != EATING) self [i].wait;
}
void putdown (int i) {
state[i] = THINKING;
// test left and right neighbors
test((i + 4) % 5);
test((i + 1) % 5);
}
Solution to Dining Philosophers (2)
void test (int i) {
if ( (state[(i + 4) % 5] != EATING) &&
(state[i] == HUNGRY) &&
(state[(i + 1) % 5] != EATING) ) {
state[i] = EATING ;
self[i].signal () ;
}
}
initialization_code() {
for (int i = 0; i < 5; i++)
state[i] = THINKING;
}
}
Solution to Dining Philosophers (3)
• Each philosopher I invokes the operations
pickup() and putdown() in the following
sequence:
Philospher(int i) {
while (1) {
Think()
dp.pickup (i)
eat()
dp.putdown (i)
}}
Solution to Dinning Philosophers (4)
• Is Deadlock Possible?
• Is Starvation possible?
Q&A