Critical Problem Revisit Critical Sections • Mutual exclusion Only one process can be in the critical section at a time Without mutual exclusion, results of multiple execution are not consistent • There is a race to execute critical sections • The sections may be defined by different code in different processes. Need an OS mechanism so programmer can resolve races Some Possible OS Mechanisms Disable interrupts, Synchronization hardware Software solution – locks, semaphores, monitors Disabling Interrupts shared double balance; Code for pi Code for pj disableInterrupts(); balance = balance + amount; enableInterrupts(); disableInterrupts(); balance = balance - amount; enableInterrupts(); Disabling interrupts guarantees mutual exclusion, but … Disadvantages A user process can easily abuse this privilege and hence should not be available to a user process. Interrupts could be disabled arbitrarily long We only want to prevent pi and pj from interfering with one another; this prevents any other process pk to execute In a Multiprocessor system, disabling interrupts in one processor will not disable it in another process and hence mutual exclusion is not guaranteed Hardware Synchronization TestAndSet() Swap() How to use Hardware Synchronization to achieve mutual exclusion and bounded waiting Lock shared boolean lock = FALSE; Code for pi . . . acquire(lock); Code for pj . . . acquire(lock); <execute critical section>; release(lock); . . . <execute critical section>; release(lock); . . . However, acquire(lock) and release(lock) operations must be atomic ! Important considerations for software locks Mutual exclusion: Only one process at a time in the Critical Section (CS) A process should not be delayed access to a critical section when there is no other process using it Once a process attempts to enter its CS, it should not be postponed indefinitely NO STARVATION! (After requesting entry, only a bounded number of other processes may enter before the requesting process) Semaphore • A semaphore, s, is a integer variable that can only be changed or tested by these two atomic (indivisible / uninterruptable) functions: P(s) (wait(s)) V(s) (signal(s)) Semaphore solution struct semaphore { int value; process *queue; } P(semaphore s): disable_interrupts(); { s.value--; if (s.value < 0) {place this process in s.queue; block this process } get( ) P( ) wait( ) pthread_mutex_lock() enable_interrupts(); } V(semaphore s): {disable_interrupts(); s.value++; if (s.value <= 0) { remove a process P from s.queue; wakeup P; } enable_interrupts(); } release( ) V( ) signal( ) pthread_mutex_unlock() Shared Account Problem Pi() { . . . Pj() { . . . /* Enter the CS */ /* Enter the CS */ P(mutex); balance += amount; V(mutex); . . . } P(mutex); balance -= amount; V(mutex); . . . } semaphore mutex = 1; pthread_create(P0, 0); pthread_create(P1, 0); Processing Two Critical Sections shared lock1 = FALSE; shared lock2 = FALSE; shared lock1 = FALSE; shared lock2 = FALSE; Code for p1 Code for p2 . . . /* Enter CS-1 */ P(lock1); <critical section 1>; V(lock1); . . . /* Enter CS-2*/ P(lock2); <critical section 2>; V(lock2); <other computation>; <other computation>; /* Enter CS-2 */ /* Enter CS-1 */ P(lock2); <critical section 2>; V(lock2); . . . P(lock1); <critical section 1>; V(lock1); . . . Deadlock may occur if locks are not used properly! shared boolean lock1 = FALSE; shared boolean lock2 = FALSE; Code for p1 . . . P(lock1); Code for p2 . . . P(lock2); <delete element>; <update length>; /* Enter CS to update length */ /* Enter CS to add element */ P(lock2); <update length>; P(lock1); <add element>; V(lock2); Vlock1); V(lock1); . . . V(lock2); . . . Classical Problems of Synchronization Classical Problems of Synchronization • Bounded-Buffer Producer/Consumer Problem • Readers and Writers Problem • Dining Philosophers Problem Bounded-Buffer Producer/Consumer Problem • Shared data: semaphore full, empty, mutex • Initially: full = 0, empty = n, mutex = 1 where n is the buffer size Bounded-Buffer Producer/Consumer Problem We must make sure that the producer and the consumer make changes to the shared buffer in a mutually exclusive manner do { … produce an item … P(empty); P(mutex); … add the item to the buffer … V(mutex); V(full); } while (TRUE); The producer must wait for an empty space in the buffer Bounded-Buffer Producer/Consumer Problem The consumer must wait for a filled space in the buffer do { P(full) P(mutex); … remove an item from the buffer … V(mutex); V(empty); … consume the item … } while (TRUE); We must make sure that the producer and the consumer make changes to the shared buffer in a mutually exclusive manner Readers/Writers Problem W R R R • Motivation: Consider a shared database – Two classes of users: • Readers – never modify database • Writers – read and modify database – Is using a single lock on the whole database sufficient? • Like to have many readers at the same time • Only one writer at a time Readers/Writers Problem • A database is to be shared among several concurrent processes. Some of these processes may want only to read the database, whereas others may want to update the database • We distinguish between these two types of processes by referring to the former as readers and to the latter as writers • Obviously, if two readers access the shared data simultaneously, nothing bad will happen • However, if a writer and some other process (either a reader or a writer) access the database simultaneously, chaos may ensue Readers/Writers Problem • To ensure that these difficulties do not arise, we require that the writers have exclusive access to the shared database • This synchronization problem has been used to test nearly every new synchronization primitive • There are several variations of this problem, all involving priorities – The first and simplest one, referred to as the first readers/writers problem, requires that no reader will be kept waiting unless a writer has already obtained permission to use the shared object (i.e., no reader should wait for other readers to finish simply because a writer is waiting) NOTE: writers may starve – The second readers/writers problem requires that, once a writer is ready, that writer performs its write as soon as possible (i.e., if a writer is waiting, no new readers may start reading) NOTE: readers may starve First Readers/Writers Problem • Shared data: semaphore mutex, wrt int readcount • Initially: mutex = 1, wrt = 1, readcount =0 First Readers/Writers Problem do { P(wrt); … writing is performed … V(wrt); } while (TRUE); A writer will wait if either another writer is currently writing or one or more readers are currently reading First Readers/Writers Problem do{ We must make sure that readers update the shared variable readcount in a mutually exclusive manner P(mutex); readcount++; if (readcount == 1) P(wrt); V(mutex); … reading is performed … P(mutex); readcount--; if (readcount == 0) V(wrt); V(mutex); } while(TRUE); A reader will wait only if a writer is currently writing. Note that if readcount == 1, no reader is currently reading and thus that is the only time that a reader has to make sure that no writer is currently writing (i.e., if readcount > 1, there is at least one reader reading and thus the new reader does not have to wait) First Reader/Writer Solution Reader: do{ P(mutex); readcount++; if (readcount == 1) P(wrt); V(mutex); … reading is performed … P(mutex); readcount--; if (readcount == 0) V(wrt); V(mutex); } while(TRUE); Writer: do { P(wrt); … writing is perform … V(wrt); } while (TRUE); • First reader competes with writers • Last reader signals writers • Any writer must wait for all readers • Readers can starve writers • “Updates” can be delayed forever not desirable! Favor Writer reader() { writer() { while(TRUE) { <other computing>; while(TRUE) { <other computing>; P(mutex2); writeCount++; if(writeCount == 1) P(RD); V(mutex2); P(WRT); access(resource); V(WRT); P(mutex2) writeCount--; if(writeCount == 0) V(RD); V(mutex2); } P(RD); P(mutex1); readCount++; if(readCount == 1) P(WRT); V(mutex1); V(RD); access(resource); P(mutex1); readCount--; if(readCount == 0) V(WRT); V(mutex1); } } } int readCount=0, writeCount=0; semaphore mutex1=1, mutex2=1; semaphore RD=1,WRT=1 • Writers can starve readers • “Reads” can be delayed forever Not desirable, either ! Dining Philosophers Problem • Five philosophers spend their lives thinking and eating • When a philosopher gets hungry, he/she tries to pick up the two chopsticks that are closest to him or her. He/she may pick up only one chopstick at a time. When finished eating, he/she puts down both chopsticks and starts thinking • A classic synchronization problem. It is an example of a large class of concurrency-control problems. It is a simple representation of the need to allocate several resources among several processes in a deadlock-free and starvationfree manner Dining Philosophers Problem • Shared data semaphore chopstick[5] Initially all values are 1 Dining Philosophers Problem – Philosopher i do { P(chopstick[i]) P(chopstick[(i+1) % 5]) … eat … V(chopstick[i]); V(chopstick[(i+1) % 5]); … think … } while (TRUE); A philosopher must wait for his/her left and right chopsticks to be available before he/she can start eating Dining Philosophers Problem • This solution guarantees that no two neighbors can be eating simultaneously (i.e., mutual exclusion) • Do you see any problem(s) with this solution? – This solution could create a deadlock. How?