Example of use Shared var mutex: semaphore = 1; Process i P(mutex); execute CS; V(mutex); End;
Other synchronization problems Semaphore can be used in other synchronization problems besides Mutual Exclusion The Producer-Consumer problem a finite buffer pool is used to exchange messages between producer and consumer processes The Readers-Writers Problem reader and writer processes accessing the same file The Dining Philosophers Problem five philosophers competing for a pair of forks
Readers-Writers solution with Procedure reader concurrent reader access P(reader_mutex) if readers = 0 then readers = readers + 1 P(writer_mutex) else readers = readers + 1 V(reader_mutex) <read file> Procedure writer P(writer_mutex) <write file> V(writer_mutex) P(reader_mutex) readers = readers - 1 if readers == 0 then V(writer_mutex) V(reader_mutex)
Readers-Writers with reader s priority Procedure reader Procedure writer P(reader_mutex) if readers = 0 then readers = readers + 1 P(writer_mutex) else readers = readers + 1 V(reader_mutex) P(sr_mutex) P(writer_mutex) <write file> V(writer_mutex) V(sr_mutex) <read file> P(reader_mutex) readers = readers - 1 if readers == 0 then V(writer_mutex) V(reader_mutex)
Producer-Consumer: solution #1 Process producer while count = N ; P(mutex) count = count + 1 write(head_ptr) head_ptr = (head_ptr + 1) mod N V(mutex) Process consumer while count = 0 ; P(mutex) count = count - 1 read(tail_ptr) tail_ptr = (tail_ptr + 1) mod N V(mutex) Semaphore mutex ensures mutual exclusion in accessing the pool, however solution shown is not correct because variable count is not protected (for example two producers could enter when count = N-1)
Producer-Consumer: Correct Solution Process producer P(mutex) if count = N then V(mutex); P(mutex_p); P(mutex) else P(mutex_p) ; count = count + 1 write(head_ptr) head_ptr = (head_ptr + 1) mod N V(mutex_c) V(mutex) Process consumer P(mutex) if count = 0 then V(mutex); P(mutex_c); P(mutex) else P(mutex_c) ; count = count - 1 read(tail_ptr) tail_ptr = (tail_ptr + 1) mod N V(mutex_p) V(mutex) Initialize: count = 0; mutex_c = 0; mutex_p = N ; Assertions count == mutex_c ; count + mutex_p = N
Producer-Consumer: another solution?? Process producer P(mutex) P(mutex_p) ; count = count + 1 write(head_ptr) head_ptr = (head_ptr + 1) mod N V(mutex_c) V(mutex) Process consumer P(mutex) P(mutex_c) ; count = count - 1 read(tail_ptr) tail_ptr = (tail_ptr + 1) mod N V(mutex_p) V(mutex) Initialize: count = 0; mutex_c = 0; mutex_p = N ; Assertions count == mutex_c ; count + mutex_p = N Does not work DEADLOCK!!
Pros: Semaphore: pros and cons no waste of resources due to busy waiting flexible resource management using an initial value > 1 Cons: processes using semaphores must be aware of each other and coordinate respective use of semaphores insertion of P and V calls is tricky and prone to errors correctness of program using semaphores can be very hard to verify do not scale up well - ie impractical for large scale use
Monitors: definition Monitors are abstract data types for encapsulating shared resources A monitor consists of: shared objects and local variables, a set of procedures Basic properties of the monitor procedures are the only operations that can be performed on the resource and on the local variables only one process at a time can be active (ie executing a procedure) within a monitor
Monitors: condition variables Condition variables are variables on which two operations are defined, wait and signal: syntax: <variable>wait and <variable>signal They are used to delay and resume execution of processes calling monitor s procedures Condition variables are visible only from within monitor procedures
Semantic of wait and signal A queue is associated with each condition variable <variable>queue returns true if queue is not empty The <variable>wait call suspends the calling process calling process relinquishes control of the monitor calling process is enqueued on the variable s queue The <variable>signal call causes one waiting process to gain control of the monitor it resume execution from where it left (ie right after the wait statement) the calling process is enqueued on the urgent queue
Producer-Consumer problem circular_pool: monitor pool: array 0N-1 of buffer; count, head, tail: int; nonemtpy, nonfull: condition; Procedure extract(x) if count = 0 then nonemptywait; x:= pool[tail] ; tail := tail + 1 mod N; count := count - 1; nonfullsignal end count := 0 head := 0; tail := 0; end circular_pool Procedure insert(x) if count = N then nonfullwait; pool[head] := x; head := head + 1 mod N; count := count + 1; nonemptysignal end
Readers-Writers: base version procedure Read; <read file> end Read; procedure Write; <write file> end Write;
Readers-Writers with concurrent reader access procedure startread readers = readers+1; end <READ FILE> procedure writer if (readers >0) then writerwait; <WRITE FILE> end procedure endread readers = readers -1; if (readers == 0) then writersignal; end This solution works, but does not guarantee readers priority hint: who is allowed into the monitor when a writer exits?
Readers-Writers solution with procedure startread; if busy then OKtoreadwait; readcount := readcount + 1; OKtoreadsignal; end startread; readers priority procedure endread; readcount := readcount - 1; if readcount = 0 then OKtowritesignal; end endread; procedure startwrite; if busy OR readcount 0 then OKtowritewait; busy := true; end startwrite; procedure endwrite; busy := false; if OKtoreadqueue then OKtoreadsignal else OKtowritesignal; end endwrite;
wait with priority An enhanced version of the wait operation accepts an optional priority argument: syntax: <variable>wait <parameter> the smaller the value of the parameter, the highest the priority When the variable is signaled, the process with highest priority in the queue is activated the base wait implementation used a First-In-First-Out (FIFO) discipline
Example: Smallest job first procedure startprint; if NOT printerisbusy then jobavailablewait; printer-file := buffer; end startprint; <print printer-file> procedure endprint; printerisbusy := false; OKtoprintsignal; end endprint; procedure enqueuejob(file); if printerisbusy then OKtoprintwait sizeof(file); printerisbusy := true; buffer := file; jobavailablesignal end;
Monitors: pros and cons Pros: encapsulation provides automatic serialization flexibility in blocking and unblocking process execution within monitor procedures Cons lack of concurrency if monitor encapsulates shared resources possibility of deadlock with nested monitor calls
Lessons learned Encapsulation of critical section of code is desirable provides automatic mutual exclusion single copy of code, single point of synchronization however would be nice to have some form of controlled concurrency Blocking/unblocking of processes is powerful tool basic ingredient are named queues, enqueue and dequeue operations enqueue and dequeue operations usually subject to condition