Semaphores • Semaphores are a convenient mechanism to not only solve the mutex problem but to also synchronize the activities of multiple threads •Semappygjhores were devised by Edsgar Djikstra in the 1960s when working on a multithreaded oppgyerating system • We will use semaphores to solve four classic concurrency problems: producers and consumers using a bounded buffer, dining philosophers, the sleeping barber, and the readers-writers in a database The Binary • A binary semaphore has the following characteristics – It only has two values, 0 (meaning the resource is in use) and 1 (meaning the resource is available) – It is initialized to 1 and can only be changed by the operations P (from the Dutch passeren meaning t)dV(fthDthto pass) and V (from the Dutch vrygeven meaning to release) – Java semap hores use the me tho ds acqu ire (operates like P) and release (operates like V) The P and V Operations • Every semaphore has a waiting queue associated with the semaphore; this queue is initially empty • Both P and V operate atomically and have the following behavior expressed in pseudocode: P(Semaphore s) V(Semaphore s) wait until s = 1 if queue is not empty s = s-1 wake up a task on queue end P else s = 1 end V • There is no implied order in waking up a waiting task but if the queue is a strict FIFO queue then the task waiting longest is woken up • If it is a priority queue then the task with the highest priority is woken up Solving the Mutex Problem • We assume a shared memory machine where the binary semaphore has been initialized to 1 • Before a critical region each calls the P oppqyeration to request entry and then calls the V operation to release the critical resource • In pseudocode Using Java syntax P(S) S.acquire() critical region critical region V(S) S.release() • This insures only one thread is in the critical region at a time Implementing Semaphores in Java • Semaphores are normally implemented at the level; here we describe a high level solution in Java • It is based on a busy-wait,,, which, of course, is inefficient • In your programs you will use the Semaphore class available as a Java API Busy-Wait Implementation class Semaphore { private int value; public Semaphore() { value = 0; } public Semaphore(int val) { value = val; } public void P() { wantToEnterCritical(); // need a parameter??? while(value == 0) { fiihdICitil()finishedInCritical(); wantToEnterCritical(); } value--; finishedInCritical(); } public void V() { wantToEnterCritical(); value++; finishedInCritical(); } } The Semaphore Class - 1 • The semaphore class was added to Java with version 1.5 • Using this class avoids wasteful busy-wait loopppys as shown previously

Constructor Summary Semaphore(int permits) Creates a Semaphore with the given number of permits and nonfair fairness setting. Semaphore(int permits, boolean fair) Creates a Semaphore with the given number of permits and the given fairness setting. The Semaphore Class - 2

Method Summary void acquire() Acquires a permit from this semaphore, blocking until one is available, or the thread is interrupted. void acquire(int permits) Acquires the given number of permits from this semaphore, blocking until all are available, or the thread is interrupted. void acqqpyuireUninterruptibly() Acquires a permit from this semaphore, blocking until one is available. void acquireUninterruptibly(int permits) Acquires the given number of permits from this semaphore, blocking until all are available. int availablePermits() Returns the current number of permits available in this semaphore. int drainPermits() Acquires and returns all permits that are immediately available. protected Collection getQueuedThreads() Returns a collection containing threads that may be waiting to acquire. int getQueueLength() Returns an estimate of the number of threads waiting to acquire . boolean hasQueuedThreads() Queries whether any threads are waiting to acquire. The Semaphore Class - 3

boolean isFair() Returns true if this semaphore has fairness set true. protected void reducePermits(int reduction) Shrinks the number of available permits by the indicated reduction. void release() Releases a permit, returning it to the semaphore. void release(int permits) Releases the given number of permits, returning them to the semaphore. String toString() Returns a string identifying this semaphore, as well as its state. boolean tryAcquire() Acquires a permit from this semaphore, only if one is available at the time of invocation. boolean tryAcquire(int permits) Acquires the given number of permits from this semaphore, only if all are available at the time of invocation. boolean tryAcquire(int permits, long timeout, TimeUnit unit) Acquires the given number of permits from this semaphore, if all become available within the given waiting time and the current thread has not been interrupted. boolean tryAcquire(long timeout, TimeUnit unit) Acquires a permit from this semaphore, if one becomes available within the given waiting time and the current thread has not been interrupted. The Dining Philosophers • Philosophers live a boring life; they think; eat; think; eat; and continue this cycle for their entire lives • Philosoppgphers have assigned places around the dining table; they are health nuts so they onlyyp eat salad which is placed in the center • Each philosopher has a possible chopstick to his/her right and another chopstick to his/her left; it takes two chopsticks to eat and they can only use their assigned two chopsticks • Chopsticks cannot be shared simultaneously The Dining Philosopher • The dining table with five philosophers

0

4 1 saldlad

3 2 A First Attempt at a Solution • Suppose the chopsticks have the following ids: the chopstick to the right of philosopher j is labeled j; the chopstick to the left is labeled j+1 mod n (in our problem n = 5) • Each philosopher j follows this routine after enteringgg and sitting at the table: pppick up the chopstick on the right; pickup the chopstick on the left;;;p eat some salad; put down the chopstick on the right; put down the chopstick on the left;;gg leave the dining room to go think • Are their any problems with this ? In-Class Lab Activity • Test the given code of picking up first the right chopstick and then the left chopstick for possible • Make deadlock more likelyyy by introducin g a delay between picking up the two chopsticks • This lab activity is described in more detail on a separate lab sheet Problem and Possible Solution • Problem: deadlock may occur if simultaneously each philosopher picks up his/her right chopstick but cannot pick up the left chopstick (it is being held by the philosopher to the left) • Possible Solution: onlyyp pick u p two chopsticks at the same time and put down two chopp;pysticks at the same time; wait patiently until both chopsticks are available since each ppphilosopher eventuall ypgy stops eating • Is there a problem with this solution? The Starving Philosopher • It is possible for the philosophers on each side of a particular philosopher to starve their colleague; suppose philosopher 2 wants to eat • P1 is eatinggp so P2 waits for both chopsticks • P1 only puts down his/her chopsticks after P3 starts eating; P2 cannot get both chopsticks • P3 only puts down his/her chopsticks after P1 comes back to the dining room and starts eating; P2 cannot get both chopsticks • P1 an d P3 con tinue this be hav ior litera lly starving P2 to death Another Way to Prevent Deadlock • There are several ways to prevent deadlock, some solutions involve picking up chopsticks one at a time, but having some philosophers pick up the right chopstick first and others pick up the left chopstick first • We will introduce an even simppyler solution by limiting the number of philosophers in the dining room to n-1 at a time • We need to introduce a general counting semaphore to implement this solution A General Counting Semaphore • The semaphore is initialized to some value n that is greater than 0; this means that n tasks can access the critical resource at a time • The semappygyhore value can only be changed by the atomic operations P and V P(Semaphore s) V(Semaphore s) wait untiuntills>0 s > 0 if queue is not empty s = s-1 wake up a task on queue end P else s = s + 1 end V Limiting Access to the Dining Room • A simple solution to the possible starvation problem with the dining philosophers is to limited access to the dining room to n-1 philosophers • Suppose the semaphore diningroom has been initialized to n-1 then the eat task would be:

eat(int philosopherID) P(diningroom) // code for getting chopsticks and eating V(diningroom) end eat In-Class Lab Activity • Introduce a counting semaphore into your dining philosopher program • Verify that limited the number of philosophers in the room to n-1 breaks the deadlock caused by the delays you introduced in the prior lab • Verify that if n philosophers are allowed in the room then the deadlock reappears • This lab activity is described in more detail on a separate lab sheet The Producer-Consumer Problem • Producers generate data and consumers fetch and use data; these processes often occur at different rates so the data is buffered

P1 C1

P2 C2 BOUNDED BUFFER P3 C3

Pm Cn The Need for a Semaphore • Two conditions must be blocked: any producer trying to put something in a full buffer and any consumer trying to fetch from an empty buffer • The spppaces semaphore counts the number of empty spaces and is initialized to the buffer size;;p the elements semaphore counts the number of items in the buffer; it is intialized to 0 deposit(item) fetch() P(spaces) P(elements) add item to buffer get item from buffer V(elements) V(spaces) end deposit return item end fetch Expanded Solution • Semaphores – spaces is a counting semaphore for the number of emppyty slots in the buffer ; initialized to numSlots – elements is a counting semaphore for the number of items in the buffer; initialized to zero – mutex is a binary semaphore that protects updates to the shared variable count • The d epos it an d fe tch meth o ds deposit(value) fetch() P(spaces) P(elements) P(t)P(mutex) P(t)P(mutex) add value to buffer remove item from buffer increment count decrement count V(mutex) V(mutex) V(elements) V(spaces) end deposit return item end fetch Problems with Semaphores • Many problem solutions require multiple semaphores; if the order of the P and V operations is c hange d, the so lu tion may no t work • ElExample: deposit(value) fetch() P(spaces) P(mutex) P(mutex) P(elements) add value to buffer remove value from buffer increment count decrement count V(mutex) V(mutex) V(elements) V(spaces) end deposit return value end fetch • Ch?Can you spot the error? The Sleeping Barber Problem • The bar ber s hop has a cutt ing room w it h a s ing le barber and one barber chair • There i s a waiti ng room with a fixe d num ber o f c ha irs for customers to wait • If a customer arrives and the waiting room is full; the customer leaves and comes back later • When the barber finishes with the current customer he gets the next customer from the waiting room or takes a nap in the barber chair if no customers are waiting • If the waiting room is empty when a customer arrives and the barber is nappin g, the customer wakes the barber and then gets into the barber chair (after the barber gets out!) The Barber Shop The semaphores and variables • waiting is an integer variable that counts the number of waiting customers; it is initialized to zero • mutex is a binary semaphore protecting changes in the variable waiting • customers is a counting semaphore that is initialized to zero; it will be incremented by the V operation every time there is room for the customer in the waiting room • cutting is a counting semaphore initialized to zero; the barber waits on the semaphore cutting, the customer signals the barber to start cutting after getting into the chair • barber is a counting semaphore that is initialized to zero; when signaled by the barber it allows the next customer to get into the barber chair for a haircut; the customer blocks on this semaphore until signaled by the barber Barber’s wantToCut method • Pseudocode wantToCut P(customers) // wait for customer P(mutex) decrement waiting V(barber) V(mutex) P((g)//cutting) // cut the hair end wantToCut Customer’s wantHairCut method • Pseudocode wantHairCut(int i) P(mutex) // check waiting room if(waiting < numChairs) increment waiting V(customers) // take a seat V(mutex) P(barber) // wait for barber V(cutting) // get haircut else V(mutex) end if end wantHairCut In-Class Lab Activity • You are given a complete program for the sleeping barber problem, however the code for the methods wantToCut and wantHaircut is missing • Complete these methods in accordance with the pseudocode on the previous slides • This lab activity is described in more detail on a separate lab sheet Multiple Sleeping Barbers • It is possible to extend the sleeping barbers to have multiple barbers, each with his own room and chair, but all sharing the same waiting room • Call the number of chairs in the waiting room n,,, the number of customers m, and the number of barbers k • To make this realistic, you should set the number of customers m to a value greater than the total number of chairs, n+k for waiting room chairs and barber chairs The Readers-Writers Problem • It is assumed the write operation changes the underlying data structure, so it must be done atomically, but the read operation does not change the data structure, so multiple reads can be done simultaneously • To enforce for both reads and writes would work but not be efficient since simultaneous reads are possible without danger • What are your ideas on how to solve this problem efficiently? A First Solution - 1 • There will be an integer count of the current number of readers; it is initialized to zero • There will be two binary semaphores, we call one mutex and it will protect changes to the count of the number of readers • the other semaphore will be called ok; it will ensure that nothing else accesses the data during the write operation • Readers will use the following methods: startRead(int i) endRead(int i) P(mutex) P(mutex) increment numReaders decrement numReaders if(numReaders == 1) P(ok) if(numReaders == 0) V(ok) V(mutex) V(mutex) end startRead end endRead A First Solution - 2 • Writers will use the following methods: startWrite(int i) endWrite(int i) P(ok) V(ok) end startWrite end endWrite • Carefully explain when and where a reader waits on a semaphore (hint: readers can wait on either mutex or ok, so distinguish these situations) • There is a serious shortcoming to this first solution; can you spot what it is? • During the after-class lab activity you will attempt to fix this problem After-Class Lab Activity • If there are multiple readers and only one writer, it is possible the writer can be starved • You are given a complete program that demonstrates this behavior • Try to avoid this starvation by not allowing any new readers once a writer appears • This lab activity is described in more detail on a separate lab sheet