Cooperating and Concurrent Processes: executions overlap in time and they need to be synchronized
Synchronization is necessary for:
multiprogrammed uniprocessor
uniprocessor and I/O processor
shared memory multiprocessor
non-shared memory systems (distributed environments)
Support for Synchronization: hardware: interrupts (shared memory support) atomic machine instructions (shared memory support) e.g. SUB R1, x
Test and Set (shared memory support)
operating system software (shared memory support) semaphores (shared memory support) message passing (non-shared memory support)
programming languages monitors (shared memory support)
Concurrent Pascal, Modula, Mesa, Concurrent Euclid path expressions (shared memory support)
Path Pascal
RPC/rendezvous(non-shared memory support)
ADA(DOD) for rendezvous
Example: Producer/Consumer problem bounded buffer holds n items no guarantee on relative speeds producer uses IN to keep track of next place to put item
consumer uses OUT to keep track of next place to take item from
if look at code, only (n-1) buffers can be used at a time
add count in order to remedy problem
both use count to know how many items are in buffer
18
in consumer: count--
LD R2, Count
SUB R2, 1
ST R2, Count
in producer: count++
LD R4, Count
ADD R4, 1
ST R4, Count
Race Condition - outcome depends on order in which instructions executed this should NOT be the case for example, what if context switch after loading of count value? need two instructions to be executed ATOMICALLY but only guaranteed that the machine instructions are atomic
The Critical Section (CS) Problem n processes competing to use some shared data
no assumption allowed on relative speeds of the n processes
CS is a segment of code which contains shared data only one process can be in its CS at a time each CS execution must be mutually exclusive (mutex)
mutual exclusion occurs if a process is in its CS, no other process is
entry to CS
CS
exit from CS
other code
Correctness: mutual exclusion - if a process is in CS, no other process in CS progress - if a process wants to enter CS decision not based on processes in other code section above bounded waiting - finite bound of number of times other process gets into CS once you request to get in therefore no: starvation - there exist a process who never gets into CS deadlock - two or more processes waiting for an event that won't occur
19
Software Solutions
Suppose two processes P2 and P3 both execute the same code to access critical section
Solution 1: assume use a shared-memory variable boolean FLAG to enforce mutual exclusion initially FLAG = F
Process 2 repeat
Process 3 repeat while (FLAG)busy wait; while (FLAG) busy wait;
FLAG = T; FLAG = T;
critical section; critical section;
FLAG = F; FLAG = F; other code; forever; other code; forever; doesn't enforce mutual exclusion
Solution 2: assume TURN(2..3) is a shared-memory variable that keeps track of whose turn it is assume TURN is initially 2
Process 2 repeat
Process 3 repeat while (TURN != 2) busy wait; while (TURN != 3) busy wait;
critical section; critical section;
TURN = 3; TURN = 2; other code; other code; forever; forever; enforces mutual exclusion strictly alternating not a good idea if one process is slower blocked by a process not in its critical section - violates progress
20
Solution 3: assume shared memory variables FLAG: array [2..3] of boolean assume FLAG is initially F
Process 2 repeat
Process 3 repeat
FLAG[2] = T; FLAG[3] = T; while (FLAG[3]) busy wait; while (FLAG[2]) busy wait;
critical section; critical section;
FLAG[2] = F; FLAG[3] = F; other code; other code; forever; forever; satisfies mutual exclusion deadlock - P2 and P3 can wait forever
Solution 4: use the same shared-memory variables as Solution 3 additional usage of FLAG variables - initially F
Process 2 repeat
Process 3 repeat
FLAG[2] = T; while (FLAG[3])
FLAG[3] = T; while (FLAG[2])
{ FLAG[2] = F; { FLAG[3] = F; delay; delay;
FLAG[2] = T; FLAG[3] = T;
} }
critical section; critical section;
FLAG[2] = F; FLAG[3] = F; other code; forever; other code; forever; can have indefinite postponement - starvation one process can monopolize the CPU
21
Solution 5: use the same shared-memory variables as Solutions 3 and 4 add the shared-memory variable TURN(2..3) to indicate which process
Process 2 repeat
Process 3 repeat
FLAG[2] = T;
TURN = 3;
FLAG[3] = T;
TURN = 2; while (FLAG[3]) and while (FLAG[2]) and
(TURN = 3) busy wait; (TURN = 2) busy wait;
critical section; critical section;
FLAG[2] = F; FLAG[3] = F; other code; forever; other code; forever; satisfies mutual exclusion satisfies bounded waiting
Peterson's solution (1981) similar to Dekker's (1965) more complex
Solves the problem for TWO processes only
Consider n processes that want CS access
Bakery Algorithm: before entering CS, get a number holder of the smallest number enters the CS number[i] = max.(number[0], number[1], ..number[n-1]) + 1 numbers generated in increasing order of enumeration:1,2,3,3,3,4,5,5,6 if number[i] = number[j], then if (i<j), then pi is served first else Pj is served first
Bakery Algorithm from Lamport 1974 - see text for details
Disadvantages of all software solutions cumbersome busy wait difficult to extend to n processors
22
Semaphores: Dijkstra 1965
A semaphore is a synchronization tool that doesn't require busy waiting
A semaphore s is an integer variable and a queue of processes semaphore is accessible by two ATOMIC ops
P = probern = WAIT; V = verhogun = SIGNAL
P(s); s--;
/* wait */
if s < 0 then block(s);
V(s); s++;
/* signal */
if s <= 0 then wakeup(s)
block(s) - suspend process invoking it (wait) - place process on queue for s
wakeup(s) - resume one process - remove process from s queue place on ready queue simple powerful: can solve wide variety of synchronization problems doesn't require a busy wait
Critical Section Problem: Solution for n Processes
Solution 6: var mutex: semaphore = 1;
Process i repeat
P(mutex);
critical section;
V(mutex);
other code;
Process j repeat
P(mutex);
critical section;
V(mutex); other code;
forever; forever; mutex = 1 means there's no one in CS and no one waiting to get in mutex = 0 means there is one process in CS mutex < 0 means many processes waiting to get into CS satisfies mutual exclusion
23
satisfies bounded waiting satisfies progress
Implementation of P and V
1. uniprocessor environment: need to maintain a queue of process's PCBs waiting with block, add PCB to linked list with signal, choose process from list(most likely FCFS)
P and V are themselves critical sections! need P and V to be atomic inhibit interrupts around the P and V code disable all interrupts code for P or V enable all interrupts
if test-and-set available (hardware solution), use it
2. multiprocessor environment:
if no special hardware, use correct software solution with P/V disable all interrupts;
Peterson's entry code
code for P or V
Peterson's exit code enable all interrupts
Semaphore Types binary semaphore: integer value can only be 0 and 1 counting semaphore: integer value can range over an unrestricted range can implement a counting semaphore as a binary semaphore need multiple binary semaphores and a counting integer
Mutual Exclusion using binary semaphores
See Solution 6 above
24
Synchronization - using binary semaphores
P and V solves precedence relations of who goes next as a general synchronization tool suppose up to code S1 in P1 must finish before S2 in P2 begins shared variable synch: semaphore = 0;
P1 P2 repeat other code;
V(synch); /* Send */ code S1; repeat other code;
P(synch); /* Receive */ code s2; forever; forever;
Multiple Access in Critical Section using couting semaphores
Can use P and V to allow more than one process access resource suppose have 5 printers, then 5 processes can print
Example: need a way to regulate people in room, only three chairs available
shared variable count: semaphore = 3;
Person comes to door
P(count);
comes into room and takes an available chair
sits for a while
exits room
V(count);
3 processes can be in CS at the same time
Can use semaphores for the producer/consumer problem changing the value of counter shared var buffer: array[1..n] of whatever shared var counter: integer = 0; shared var s: semaphore = 1;
Producer/Consumer Problem: Solution 1
Producer repeat
Consumer
repeat
25
while (counter == n) do no-op; while(counter==0) do no-op;
buffer[in] = next item; consumer item in buffer[out];
in++; (mod n) out++; (mod n)
P(s); P(s); counter++;
V(s); forever; counter-;
V(s); forever;
PROBLEM: busy wait yuck!
Reader/Writer Problem: Solution 1 shared information reader: read only writer: read and write (must have exclusive access) shared-var mutex: semaphore = 1; shared-var wrt: semaphore = 1; shared-var readcount: integer = 0;
Reader Writer (only one writer at a time) repeat
P(mutex); readcount++; if readcount = 1 then P(wrt); repeat
P(wrt); forever;
..writing is performed...
V(wrt);
V(mutex);
..reading is performed...
P(mutex); readcount--; if readcount = 0 then V(wrt);
V(mutex); forever;
who had priority in accessing data? readers
no reader will be kept waiting unless a writer has attained permission
Dining Philosophers Problem: Solution 1 each philosopher thinks and then eats then thinks pick up left chopstick, then pick up right chopstick
26
shared var chopstick: array[0..4] of semaphore = 1 repeat
P(chopstick[i]);
P(chopstick[i+1 mod 5]);
..eat...
V(chopstick[i]);
V(chopstick[i+1 mod 5]);
..think.. forever;
Problem: deadlock possible
Dining Philosophers Problem: Solution 2 each philosopher thinks and then eats then thinks... need two chopsticks to eat pick up left chopstick, if right not available, put down left
loop until are holding both, then eat shared var chopstick: array[0..4] of semaphore = 1 repeat repeat
P(chopstick[i]); /* pick up left */ if (right not available) then V(chopstick[i]); else P(chopstick[i+1 mod 5]); until holding both;
..eat ...
V(chopstick[i]);
V(chopstick[i+1 mod 5]);
...think... forever;
problem: starvation
Dining Philosophers Problem: Solution 3 only allow four philosophers to be in the room at a time shared var chopstick: array[0..4] of semaphore = 1 shared var room: semaphore = 4;
27
repeat
P(room);
P(chopstick[i]);
P(chopstick[i+1 mod 5]);
..eat ...
V(chopstick[i]);
V(chopstick[i+1 mod 5]);
V(room);
...think ... forever;
WORKS!
Possible Problems with Semaphore
Semaphores are prone to user errors! e.g. switching P's and V's perhaps better to have higher level programming language constructs monitors
Starvation is possible depending on the scheduling algorithm for removing processes from waiting list a process may never be removed from the semaphore "queue"
Deadlock - if don't have synchronization specified correctly
Example: shared variables S, Q: semaphores
each initially = 1
P2 P1 repeat
P(S); repeat
P(Q);
P(Q);
...
V(S);
V(Q);
forever;
P(S);
...
V(Q);
V(S); forever;
Critical regions: high level synchronization construct
shared var count: integer region count do S1;
28
region count do S2; compiler will generate P and V to be sure S1 and S2 are mutual exclusive
P(count:mutex) ... S1 ... V(count:mutex)
Hardware Solutions to CS problem
Disable interrupts - simplest solution allow no clock interrupts problems users will abuse (its OK for kernel but not user level processes) if 2 or more CPUs, other CPUs not affected
Computer manufacturers design hardware with special machine instructions instructions specifically for the CS problem - Test-and-Set instruction
Test-and-Set is: atomic instruction
copies the value in MEM to register REG sets MEM = 1 (True) think of test-and-set as the following function: function test-and-set (var mem: Boolean): Boolean begin indivisible
test-and-set := mem;
mem:= True;
end indivisible that maps to the following single assembly language instruction:
TandS REG, MEM
CS problem can be solved with this hardware shared memory LOCK: Boolean = False;
while (test-and-set(LOCK)) no-op; /* busy wait */
critical section
LOCK = false;
satisfies mutual exclusion because test-and-set is atomic;
satisfies progress bounded wait? NO!
29
to get bounded wait, solution twice as long - see textbook
Disadvantages of hardware solutions busy wait - spinlock spins while waiting for lock - useful in mulitprocessor context no context switch required - which takes up time useful if wait for lock only a short amount of time not applicable to computers without these special functions not able to solve more complex synchronization problems
Can implement binary semaphores using test-and-set
P (S : semaphore);
{ while test-and-set(S) do no-op;
}
V (S : semaphore);
{
S = false;
}
Monitors: Hoare 1974
High-level synchronization tool available in lots of programming languages:
First used by Concurrent Pascal (Brinch Hansen 1975)
Modula (Wirth 1979)
Concurrent Euclid (Holt)
Abstract data type with capabilities for synchronization
ADT: module that encapsulates
data (representing resources)
private procedures
public procedures
public interface
data manipulated through public interface which uses private interface
internal implementation of ADT is unknown to the user
all components of the monitor are encapsulated in the monitor structure resource, procedure, initialization code, etc.
Monitors and synchronization:
30
Only one process in a monitor at a time - mutual exclusion
condition variables - for additional synchronization suppose x: condition; is a local data structure
wait(cond): the process that executes this instruction gets suspended the process waits on a queue associated with condition x releases mutual exclusion to another
signal(cond): the process that executes this instruction blocks some require signal to be last instruction in monitor process from queue gets to execute in monitor if queue is empty, no effect from signal - this is different from a semaphore
Processes in the condition queue may be organized according to
* FCFS
* priority
* other scheduling disciplines when process calls x.wait another process allowed in monitor
Example: counter
/* Monitor counter; */ type counter = monitor; var count:integer; procedure entry incr; begin count++; end procedure entry decr; begin count--; end begin count = 0; end
/* end monitor; */
/* initialization */ in programs that need access to shared counter: var total: counter; /* declaring total as a monitor */
...
31
total.incr;
... total.decr;
Example: Producer and Consumer
/* Monitor buffer; */ type buffer: monitor; var type buf = array[0..(n-1)] of item; head, tail, counter:integer; empty, full: condition; procedure entry deposit(x:item); begin if (counter == n) then full.wait; buf[tail] = x; tail = (tail+1) mod n; counter++; empty.signal; /* if no one waiting, nothing happens */ end procedure entry remove(y:item); begin if (counter == 0) then empty.wait; y = buf[head]; head = (head+1) mod n; counter--; full.signal; end begin /* initialization */ counter = 0; head = 0; tail = 0; end
/* end monitor; */
Producer (void); var item_pr : buffer; data : item; repeat item_pr.enter(data); forever;
32
Consumer (void); var item_cons : buffer; data : item; repeat item_cons.remove(data); forever;
Examples in text: dining philosophers solution with monitors resource allocation solution with monitors one resource that requires mutual exclusive access
Monitors are equivalent in power to P and V can implement semaphores with monitors can implement monitors with semaphores text shows how to implement monitors with semaphores read carefully if not sure equivalent power
Disadvantages of monitors: if resource being access outside of monitor depends on correct use of entry procedures if using monitor to control access for reader/writer problem only one reader allowed to access shared data (only one in monitor) nested monitor calls: monitor procedure calls monitor procedure calls... deadlock and efficiency problems possible one solution: whenever block in a monitor temporarily release ownership of all monitors (increase bookkeeping, but necessary and flexible) second solution: inner monitors ensure processes wait only a short time queue handling is not clearly defined
Skip the rest of the text
33