CS 361 Concurrent programming Drexel University Fall 2004 Lecture 7 Bruce Char and Vera Zaychik. All rights reserved by the author. Permission is given to students enrolled in CS361 Fall 2004 to reproduce these notes for their own use. Pre- and post- CS protocols WantToEnterCS(.) Serves as a gatekeeper only lets one thread through at a time. While the first thread through the gate is in the critical section, all the other threads executing WantToEnterCS are prevented from leaving it (the gate is closed). FinishedWithCS( ) Notifies the gatekeeper that the thread in the CS is done; will now let another thread executing WantToEnterCS through. page 1 page 2 Two aspects of the mutual exclusion problem Implementing the pre- and post- protocols Correct iron-clad mutual exclusion (at most one thread in a critical section at a time) Works with any number of threads (1, 2, or more) Using the pre- and post-protocols to correctly provide mutual exclusion where they are needed Other requirements for a mutual exclusion algorithm No deadlock waiting to get into CS Avoidance of starvation in the absence of contention (one thread trying, never gets in). page 3 page 4 Other requirements (not always insisted upon) Avoidance of starvation in the presence of contention Multiple threads trying to get into CS, one thread never gets in No livelock: threads take an excessive amount of time fighting through congestion to get in Fairness: no threads favored repeatedly if there is contention Software providing mutual exclusion In the book s discussion of solutions to the mutual exclusion problem, Hartley assumes Standard load/store register architecture Multiply executing concurrent threads that share data Single or multiple CPUs that may be identical or different in speeds Access to shared variables can be interleaved if two threads get into their critical sections in time. page 5 page 6
More assumptions Threads do not halt or crash in their pre/post protocols, or in their critical sections, but may halt or crash outside of critical sections. Hardware support for mutual exclusion Definition of an atomic instruction a machine language instruction that is executed completely without being interruptible no interleaving of other instructions from another thread no context switching no hardware interrupts. page 7 page 8 page 9 Software solutions for mutual exclusion: attempt #1 Use busy waiting. boolean lockflag = false; WantToEnterCS(int I) { // thread #I wants to enter while(lockflag) {; // busy wait // loops until lockflag is false; // hopefully other thread will set this false. lockflag=true; finishedincs(int I) { lockflag = false; What s wrong with this? page 10 Problem: we can have a race condition ThreadA: load lockflag into R Context switch from thread A to B ThreadB: load lockflag into R Compare value: it s false Store true to lockflag Enter critical section Context switch from thread B back to A Thread A: compare R and value: it s false, so proceed Store true to lockflag Enter critical section So both can be in critical section at same time. Attempt #2 Use a turn variable: int turn =0; is either 0 or 1 public void WanttoEnterCS(int i) { while (turn!=i) // go from running back to run queue give someone else a // chance. What s the matter with this? Hint -- what happens if there s only one thread that wants to run? This is Starvation in the absence of contention. public void finishedincs(int i) { turn = other(i); // this sets I to something other than i page 11 page 12
Attempt #3 // Use a flag class again. class Flag {public volatile boolean value = false; // In class that handles mutual exclusion.. private Flag[] desirecs = new Flag[2]; // array of flags. for (int I = 0; I<2; I++) desirecs[i] = new Flag(); // initialize with new flag objects while (desirecs[other(i)].value) Thread.currentthread().yield(); // busy wait // set your flag What s wrong with this? Hint: race condition. If both threads execute the while at the same time, neither flag will yet be set, so they will both proceed into the critical section. So there s no mutual exclusion. page 13 page 14 Attempt #4 Exercise page 15 Try fixing flags while (desirecs[other(i)].value) { post-protocol FinishedWithCS same as before This doesn t work as a way of ensuring mutual exclusion. Describe a scenario where the code fails to work properly. Can you find more than one kind of scenario where the code fails? page 16 Attempt #4 Checklist page 17 Try fixing flags while (desirecs[other(i)].value) { post-protocol FinishedWithCS same as before Does this have a starvation in the absence of contention problem? Does this have a mutual exclusion problem? Deadlock? Starvation in the presence of contention? Livelock? page 18
Deadlock. What s wrong with this? Deadlock problem with #4 Both threads set their flags at more or less the same time Both threads enter the while loop and see that the other s flag is set. They then busy wait forever. page 19 page 20 Use of volatile setting for flag Book says it s important in how this attempt enforces mutual exclusion. Why? Use of volatile setting for flag Without the volatile setting, both threads could set their flags, but neither would see the other s flag set, so both would enter the CS. page 21 page 22 Attempt #5 while (desirecs[other(i)].value) { desirecs[i].value = false; // back off desirecs[i].value = true; What s wrong with this? Livelock -- both threads back off and try again in lockstep Hartley claims that this livelock is unlikely (certain?) to last forever, due to differences in time slicing and clock speed. So this would be considered almost correct but with an efficiency defect in that the livelock will sometimes slow down entry into the CS. page 23 page 24
Dekker s attempt // preprotocol while (desirecs[other(i)].value) { if (turn!=i) { // take turns backing off. desirecs[i].value = false; // back off while (turn!=i) // busy wait desirecs[i].value=true; // end if // end while // postprotocol desirecs[i].value = false; turn = other(i); page 25 page 26 What kinds of problems, if any, does Dekker s algorithm have with Enforcement of mutual exclusion Deadlock/livelock Starvation in the presence of contention Starvation in the absence of contention The semantics of yield() Correctness public static void yield() Causes the currently executing thread object to temporarily pause and allow other threads to execute. Java Language Reference: In some cases, a large number of threads could be waiting on the currently running thread to finish executing before they can start executing. To make the thread scheduler switch from the current running thread to allow others to execute, call the yield() method on the current thread. In order for yield() to work, there must be at least one thread with an equal or higher priority than the current thread.. page 27 While it is sufficient to find a scenario to prove that something doesn t work, To prove that an algorithm is correct, you must show that it works under all possible scenarios. Usually the way that this is done is to show that it is impossible for it to behave incorrectly. This involves insight, using a combination of logic and programming language semantics. page 28 Peterson s attempt (mutual exclusion for two threads) // preprotocol last = other(i); while (desirecs[other(i)].value && last == other(i)) // busy wait // postprotocol desirecs[i].value=false; Proof of correctness Prove that the algorithm enforces mutual exclusion correctly by showing that it is impossible for two threads to be in the critical section simultaneously. doesn t allow deadlock (i.e. it s impossible). doesn t allow starvation in the presence of contention doesn t allow starvation in the absence of contention A separate proof for each point, each done as a proof by contradiction. page 29 page 30
Proof by contradiction If you show that assuming p leads you to conclude something which you know to be false, then you are forced to conclude that there are no circumstances when p is true in other words, p must be false all the time. ( p false) p Game plan for proof by contradiction Here, we show that threads following Peterson s algorithm and ending up with a violation of ME must have impossible (false) results from their programming, such as having a variable being both 0 and 1 simultaneously. From this, we conclude that we can t assume that threads following Peterson s algorithm end up violating ME. page 31 page 32