Mutual Exclusion Companion slides for The Art of Multiprocessor Programming by Maurice Herlihy & Nir Shavit Mutual Exclusion In his 1965 paper E. W. Dijkstra wrote: "Given in this paper is a solution to a problem which, to the knowledge of the author, has been an open question since at least 1962, irrespective of the solvability. [...] Although the setting of the problem might seem somewhat academic at first, the author trusts that anyone familiar with the logical problems that arise in computer coupling will appreciate the significance of the fact that this problem indeed can be solved." Art of Multiprocessor Programming 2 Time • “Absolute, true and mathematical time, of itself and from its own nature, flows equably without relation to anything external.” (I. Newton, 1689) • “Time is, like, Nature’s way of making sure that everything doesn’t happen all at once.” (Anonymous, circa 1968) time Art of Multiprocessor Programming 3 Events • An event a0 of thread A is – Instantaneous – No simultaneous events (break ties) a0 time Art of Multiprocessor Programming 4 Threads • A thread A is (formally) a sequence a0, a1, ... of events – “Trace” model – Notation: a0 a1 indicates order a0 a1 a2 … time Art of Multiprocessor Programming 5 Example Thread Events • • • • • Assign to shared variable Assign to local variable Invoke method Return from method Lots of other things … Art of Multiprocessor Programming 6 Threads are State Machines Events are transitions a3 a2 Art of Multiprocessor Programming a0 a1 7 States • Thread State – Program counter – Local variables • System state – Object fields (shared variables) – Union of thread states Art of Multiprocessor Programming 8 Concurrency • Thread A time Art of Multiprocessor Programming 9 Concurrency • Thread A time • Thread B time Art of Multiprocessor Programming 10 Interleavings • Events of two or more threads – Interleaved – Not necessarily independent (why?) time Art of Multiprocessor Programming 11 Intervals • An interval A0 =(a0,a1) is – Time between events a0 and a1 a0 A0 a1 time Art of Multiprocessor Programming 12 Intervals may Overlap b0 a0 A0 B0 b1 a1 time Art of Multiprocessor Programming 13 Intervals may be Disjoint b0 a0 A0 B0 b1 a1 time Art of Multiprocessor Programming 14 Precedence Interval A0 precedes interval B0 b0 a0 A0 B0 b1 a1 time Art of Multiprocessor Programming 15 Precedence • Notation: A0 B0 • Formally, – End event of A0 before start event of B0 – Also called “happens before” or “precedes” Art of Multiprocessor Programming 16 Precedence Ordering • Remark: A0 B0 is just like saying – 1066 AD 1492 AD, – Middle Ages Renaissance, • Oh wait, – what about this week vs this month? Art of Multiprocessor Programming 17 Precedence Ordering • • • • Never true that A A If A B then not true that B A If A B & B C then A C Funny thing: A B & B A might both be false! Art of Multiprocessor Programming 18 Partial Orders (you may know this already) • Irreflexive: – Never true that A A • Antisymmetric: – If A B then not true that B A • Transitive: – If A B & B C then A C Art of Multiprocessor Programming 19 Total Orders (you may know this already) • Also – Irreflexive – Antisymmetric – Transitive • Except that for every distinct A, B, – Either A B or B A Art of Multiprocessor Programming 20 Repeated Events while (mumble) { a0; a1; } a0 k A0k k-th occurrence of event a0 k-th occurrence of interval A0 =(a0,a1) Art of Multiprocessor Programming 21 Locks (Mutual Exclusion) public interface Lock { public void lock(); public void unlock(); } Art of Multiprocessor Programming 22 Using Locks public class Counter { private long value; private Lock lock; public long getAndIncrement() { lock.lock(); try { int temp = value; value = value + 1; } finally { lock.unlock(); } return temp; }} Art of Multiprocessor Programming 23 Using Locks public class Counter { private long value; private Lock lock; public long getAndIncrement() { lock.lock(); acquire try { int temp = value; value = value + 1; } finally { lock.unlock(); } return temp; }} Art of Multiprocessor Programming Lock 24 Using Locks public class Counter { private long value; private Lock lock; public long getAndIncrement() { lock.lock(); try { int temp = value; value = value + 1; } finally { Release lock lock.unlock(); } (no matter what) return temp; }} Art of Multiprocessor Programming 25 Using Locks public class Counter { private long value; private Lock lock; public long getAndIncrement() { lock.lock(); try { int temp = value; value = value + 1; } finally { lock.unlock(); } return temp; }} Art of Multiprocessor Programming Critical section 26 Two-Thread vs n -Thread Solutions • Two-thread solutions first – Illustrate most basic ideas – Fits on one slide • Then n-Thread solutions Art of Multiprocessor Programming 27 Two-Thread Conventions class … implements Lock { … // thread-local index, 0 or 1 public void lock() { int i = ThreadID.get(); int j = 1 - i; … } } Art of Multiprocessor Programming 28 Two-Thread Conventions class … implements Lock { … // thread-local index, 0 or 1 public void lock() { int i = ThreadID.get(); int j = 1 - i; … } } Henceforth: i is current thread, j is other thread Art of Multiprocessor Programming 29 LockOne class LockOne implements Lock { private boolean[] flag = new boolean[2]; public void lock() { flag[i] = true; while (flag[j]) {} } Art of Multiprocessor Programming 30 LockOne class LockOne implements Lock { private boolean[] flag = new boolean[2]; public void lock() { flag[i] = true; while (flag[j]) {} Set my flag } Art of Multiprocessor Programming 31 LockOne class LockOne implements Lock { private boolean[] flag = new boolean[2]; public void lock() { flag[i] = true; while (flag[j]) {} Set my flag } Wait for other flag to go false Art of Multiprocessor Programming 32 LockOne Satisfies Mutual Exclusion • Assume CSAj overlaps CSBk • Consider each thread's last (j-th and k-th) read and write in the lock() method before entering • Derive a contradiction Art of Multiprocessor Programming 33 From the Code • writeA(flag[A]=true) readA(flag[B]==false) CSA • writeB(flag[B]=true) readB(flag[A]==false) CSB class LockOne implements Lock { … public void lock() { flag[i] = true; while (flag[j]) {} } Art of Multiprocessor Programming 34 From the Assumption • readA(flag[B]==false) writeB(flag[B]=true) • readB(flag[A]==false) writeA(flag[B]=true) Art of Multiprocessor Programming 35 Combining • Assumptions: – readA(flag[B]==false) writeB(flag[B]=true) – readB(flag[A]==false) writeA(flag[A]=true) • From the code – writeA(flag[A]=true) readA(flag[B]==false) – writeB(flag[B]=true) readB(flag[A]==false) Art of Multiprocessor Programming 36 Combining • Assumptions: – readA(flag[B]==false) writeB(flag[B]=true) – readB(flag[A]==false) writeA(flag[A]=true) • From the code – writeA(flag[A]=true) readA(flag[B]==false) – writeB(flag[B]=true) readB(flag[A]==false) Art of Multiprocessor Programming 37 Combining • Assumptions: – readA(flag[B]==false) writeB(flag[B]=true) – readB(flag[A]==false) writeA(flag[A]=true) • From the code – writeA(flag[A]=true) readA(flag[B]==false) – writeB(flag[B]=true) readB(flag[A]==false) Art of Multiprocessor Programming 38 Combining • Assumptions: – readA(flag[B]==false) writeB(flag[B]=true) – readB(flag[A]==false) writeA(flag[A]=true) • From the code – writeA(flag[A]=true) readA(flag[B]==false) – writeB(flag[B]=true) readB(flag[A]==false) Art of Multiprocessor Programming 39 Combining • Assumptions: – readA(flag[B]==false) writeB(flag[B]=true) – readB(flag[A]==false) writeA(flag[A]=true) • From the code – writeA(flag[A]=true) readA(flag[B]==false) – writeB(flag[B]=true) readB(flag[A]==false) Art of Multiprocessor Programming 40 Combining • Assumptions: – readA(flag[B]==false) writeB(flag[B]=true) – readB(flag[A]==false) writeA(flag[A]=true) • From the code – writeA(flag[A]=true) readA(flag[B]==false) – writeB(flag[B]=true) readB(flag[A]==false) Art of Multiprocessor Programming 41 Cycle! Art of Multiprocessor Programming 42 Deadlock Freedom • LockOne Fails deadlock-freedom – Concurrent execution can deadlock flag[i] = true; while (flag[j]){} flag[j] = true; while (flag[i]){} – Sequential executions OK Art of Multiprocessor Programming 43 LockTwo public class LockTwo implements Lock { private int victim; public void lock() { victim = i; while (victim == i) {}; } public void unlock() {} } Art of Multiprocessor Programming 44 LockTwo public class LockTwo implements Lock { private int victim; Let other public void lock() { first victim = i; while (victim == i) {}; } go public void unlock() {} } Art of Multiprocessor Programming 45 LockTwo public class LockTwo implements Lock { Wait for private int victim; public void lock() { permission victim = i; while (victim == i) {}; } public void unlock() {} } Art of Multiprocessor Programming 46 LockTwo public class Lock2 implements Lock { private int victim; public void lock() { Nothing victim = i; while (victim == i) {}; } to do public void unlock() {} } Art of Multiprocessor Programming 47 LockTwo Claims • Satisfies mutual exclusion – If thread i in CS public void LockTwo() { – Then victim == j victim = i; while (victim == i) {}; – Cannot be both 0 and 1 • Not deadlock free } – Sequential execution deadlocks – Concurrent execution does not Art of Multiprocessor Programming 48 Peterson’s Algorithm public void lock() { flag[i] = true; victim = i; while (flag[j] && victim == i) {}; } public void unlock() { flag[i] = false; } Art of Multiprocessor Programming 49 Peterson’s Algorithm Announce I’m interested public void lock() { flag[i] = true; victim = i; while (flag[j] && victim == i) {}; } public void unlock() { flag[i] = false; } Art of Multiprocessor Programming 50 Peterson’s Algorithm Announce I’m interested public void lock() { flag[i] = true; Defer to other victim = i; while (flag[j] && victim == i) {}; } public void unlock() { flag[i] = false; } Art of Multiprocessor Programming 51 Peterson’s Algorithm Announce I’m interested Defer to other public void lock() { flag[i] = true; victim = i; while (flag[j] && victim == i) {}; } public void unlock() { Wait while other flag[i] = false; interested & I’m } the victim Art of Multiprocessor Programming 52 Peterson’s Algorithm Announce I’m interested Defer to other public void lock() { flag[i] = true; victim = i; while (flag[j] && victim == i) {}; } public void unlock() { Wait while other flag[i] = false; interested & I’m } the victim No longer interested Art of Multiprocessor Programming 53 Mutual Exclusion public void lock() { flag[i] = true; victim = i; while (flag[j] && victim == i) {}; • If thread 0 in critical section, – flag[0]=true, – victim = 1 • If thread 1 in critical section, – flag[1]=true, – victim = 0 Cannot both be true Art of Multiprocessor Programming 54 • do código: – writeA(flag[A]=true) writeA(victim=A) readA(flag[B]) readA(victim) CSA – writeB(flag[B]=true) writeB(victim=B) readB(flag[A]) readB(victim) CSB • vamos assumir: – writeB(victim=B) writeA(victim=A) • como A entrou na RC: • writeA(victim=A) readA(flag[B] == false) – mas então • writeB(flag[B]=true) writeB(victim=B) writeA(victim=A) readA(flag[B] == false) Art of Multiprocessor Programming 55 Deadlock Free public void lock() { … while (flag[j] && victim == i) {}; • Thread blocked – only at while loop – only if it is the victim • One or the other must not be the victim Art of Multiprocessor Programming 56 Starvation Free • Thread i blocked only if j repeatedly re-enters so that flag[j] == true victim == i • When j and re-enters – it sets victim to j. – So i gets in public void lock() { flag[i] = true; victim = i; while (flag[j] && victim == i) {}; } public void unlock() { flag[i] = false; } Art of Multiprocessor Programming 57 The Filter Algorithm for n Threads There are n-1 “waiting rooms” called levels ncs • At each level – At least one enters level – At least one blocked if many try cs • Only one thread makes it through Art of Multiprocessor Programming 58 Filter class Filter implements Lock { int[] level; // level[i] for thread i int[] victim; // victim[L] for level L 2 public Filter(int n) { n-1 0 level = new int[n]; level 0 0 4 0 0 0 0 0 victim = new int[n]; 1 for (int i = 1; i < n; i++) { level[i] = 0; }} 2 4 … } Thread 2 at level 4 Art of Multiprocessor Programming n-1 victim 59 Filter class Filter implements Lock { … public void lock(){ for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i level[k] >= L) && victim[L] == i ); }} public void unlock() { level[i] = 0; }} Art of Multiprocessor Programming 60 Filter class Filter implements Lock { … public void lock() { for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i) level[k] >= L) && victim[L] == i); }} public void release(int i) { level[i] = 0; }} One level at a time Art of Multiprocessor Programming 61 Filter class Filter implements Lock { … public void lock() { for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i) level[k] >= L) && victim[L] == i); // busy wait }} public void release(int i) { level[i] = 0; }} Art of Multiprocessor Programming Announce intention to enter level L 62 Filter class Filter implements Lock { int level[n]; int victim[n]; public void lock() { for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i) level[k] >= L) && victim[L] == i); }} public void release(int i) { level[i] = 0; }} Art of Multiprocessor Programming Give priority to anyone but me 63 Filter class Filter implements Lock { Wait as long as someone else is at int level[n]; int victim[n]; higher level, and I’m designated public void lock() { for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; same or victim while (($ k != i) level[k] >= L) && victim[L] == i); }} public void release(int i) { level[i] = 0; }} Art of Multiprocessor Programming 64 Filter class Filter implements Lock { int level[n]; int victim[n]; public void lock() { for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i) level[k] >= L) && victim[L] == i); }} public void release(int i) { Thread enters level L when level[i] = 0; the loop }} Art of Multiprocessor Programming it completes 65 Claim • Start at level L=0 • At most n-L threads enter level L • Mutual exclusion at level L=n-1 ncs L=0 L=1 L=n-2 cs L=n-1 Art of Multiprocessor Programming 66 Induction Hypothesis • No more than n-L+1 at level L-1 • Induction step: by contradiction • Assume all at level L-1 enter level L • A last to write victim[L] • B is any other thread at level L ncs assume L-1 has n-L+1 L has n-L cs Art of Multiprocessor Programming prove 67 Proof Structure ncs A B Assumed to enter L-1 n-L+1 = 4 Last to write victim[L] n-L+1 = 4 cs By way of contradiction all enter L Show that A must have seen B in level[L] and since victim[L] == A could not have entered Art of Multiprocessor Programming 68 From the Code (1) writeB(level[B]=L)writeB(victim[L]=B) public void lock() { for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i) level[k] >= L) && victim[L] == i) {}; }} Art of Multiprocessor Programming 69 From the Code (2) writeA(victim[L]=A)readA(level[B]) public void lock() { for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i) level[k] >= L) && victim[L] == i) {}; }} Art of Multiprocessor Programming 70 By Assumption (3) writeB(victim[L]=B)writeA(victim[L]=A) By assumption, A is the last thread to write victim[L] Art of Multiprocessor Programming 71 Combining Observations (1) writeB(level[B]=L)writeB(victim[L]=B) (3) writeB(victim[L]=B)writeA(victim[L]=A) (2) writeA(victim[L]=A)readA(level[B]) Art of Multiprocessor Programming 72 Combining Observations (1) writeB(level[B]=L)writeB(victim[L]=B) (3) writeB(victim[L]=B)writeA(victim[L]=A) (2) writeA(victim[L]=A)readA(level[B]) public void lock() { for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i) level[k] >= L) && victim[L] == i) {}; Art of Multiprocessor }} Programming 73 Combining Observations (1) writeB(level[B]=L)writeB(victim[L]=B) (3) writeB(victim[L]=B)writeA(victim[L]=A) (2) writeA(victim[L]=A)readA(level[B]) Thus, A read level[B] ≥ L, A was last to write victim[L], so it could not have entered level L! Art of Multiprocessor Programming 74 No Starvation • Filter Lock satisfies properties: – Just like Peterson Alg at any level – So no one starves • But what about fairness? – Threads can be overtaken by others Art of Multiprocessor Programming 75 Bounded Waiting • Want stronger fairness guarantees • Thread not “overtaken” too much • Need to adjust definitions …. Art of Multiprocessor Programming 76 Bounded Waiting • Divide lock() method into 2 parts: – Doorway interval: • Written DA • always finishes in finite steps – Waiting interval: • Written WA • may take unbounded steps Art of Multiprocessor Programming 77 r-Bounded Waiting • For threads A and B: – If DAk DB j • A’s k-th doorway precedes B’s j-th doorway – Then CSAk CSBj+r • A’s k-th critical section precedes B’s (j+r)th critical section • B cannot overtake A by more than r times • First-come-first-served means r = 0. Art of Multiprocessor Programming 78 Fairness Again • Filter Lock satisfies properties: – No one starves – But very weak fairness • Not r-bounded for any r! – That’s pretty lame… Art of Multiprocessor Programming 79 Bakery Algorithm • Provides First-Come-First-Served • How? – Take a “number” – Wait until lower numbers have been served • Lexicographic order – (a,i) > (b,j) • If a > b, or a = b and i > j Art of Multiprocessor Programming 80 Bakery Algorithm class Bakery implements Lock { boolean[] flag; Label[] label; public Bakery (int n) { flag = new boolean[n]; label = new Label[n]; for (int i = 0; i < n; i++) { flag[i] = false; label[i] = 0; } } … Art of Multiprocessor Programming 81 Bakery Algorithm class Bakery implements Lock { boolean[] flag; 6 Label[] label; 2 0 n-1 public Bakery (int n) { flag = new boolean[n]; f f t f f t f f label = new Label[n]; 0 0 4 0 0 5 0 0 for (int i = 0; i < n; i++) { flag[i] = false; label[i] = 0; } } … CS Art of Multiprocessor Programming 82 Bakery Algorithm class Bakery … public void flag[i] = label[i] = implements Lock { lock() { true; max(label[0], …,label[n-1])+1; while ($k flag[k] && (label[i],i) > (label[k],k)); } Art of Multiprocessor Programming 83 Bakery Algorithm class Bakery … public void flag[i] = label[i] = implements Lock { Doorway lock() { true; max(label[0], …,label[n-1])+1; while ($k flag[k] && (label[i],i) > (label[k],k)); } Art of Multiprocessor Programming 84 Bakery Algorithm class Bakery … public void flag[i] = label[i] = implements Lock { I’m interested lock() { true; max(label[0], …,label[n-1])+1; while ($k flag[k] && (label[i],i) > (label[k],k)); } Art of Multiprocessor Programming 85 Bakery Algorithm class Bakery … public void flag[i] = label[i] = implements Lock { Take increasing label (read labels in some arbitrary order) lock() { true; max(label[0], …,label[n-1])+1; while ($k flag[k] && (label[i],i) > (label[k],k)); } Art of Multiprocessor Programming 86 Bakery Algorithm class Bakery … public void flag[i] = label[i] = implements Lock { Someone is interested lock() { true; max(label[0], …,label[n-1])+1; while ($k flag[k] && (label[i],i) > (label[k],k)); } Art of Multiprocessor Programming 87 Bakery Algorithm class Bakery implements Lock { boolean flag[n]; Someone int label[n]; is interested public void lock() { flag[i] = true; label[i] = max(label[0], …,label[n-1])+1; while ($k flag[k] && (label[i],i) > (label[k],k)); } With lower (label,i) in lexicographic order Art of Multiprocessor Programming 88 Bakery Algorithm class Bakery implements Lock { … public void unlock() { flag[i] = false; } } Art of Multiprocessor Programming 89 Bakery Algorithm class Bakery implements Lock { … No longer interested public void unlock() { flag[i] = false; } } labels are always increasing Art of Multiprocessor Programming 90 No Deadlock • There is always one thread with earliest label • Ties are impossible (why?) Art of Multiprocessor Programming 91 First-Come-First-Served • If DA DBthen A’s label is smaller • And: – writeA(label[A]) readB(label[A]) writeB(label[B]) readB(flag[A]) • So B is locked out while flag[A] is true class Bakery implements Lock { public void lock() { flag[i] = true; label[i] = max(label[0], …,label[n-1])+1; while ($k flag[k] && (label[i],i) > (label[k],k)); } Art of Multiprocessor Programming 92 Mutual Exclusion • Suppose A and B in CS together • Suppose A has earlier label • When B entered, it must have seen class Bakery implements Lock { public void lock() { flag[i] = true; label[i] = max(label[0], …,label[n-1])+1; while ($k flag[k] && (label[i],i) > (label[k],k)); } – flag[A] is false, or – label[A] > label[B] Art of Multiprocessor Programming 93 Mutual Exclusion • Labels are strictly increasing so • B must have seen flag[A] == false Art of Multiprocessor Programming 94 Mutual Exclusion • Labels are strictly increasing so • B must have seen flag[A] == false • LabelingB readB(flag[A]) writeA(flag[A]) LabelingA Art of Multiprocessor Programming 95 Mutual Exclusion • Labels are strictly increasing so • B must have seen flag[A] == false • LabelingB readB(flag[A]) writeA(flag[A]) LabelingA • Which contradicts the assumption that A has an earlier label Art of Multiprocessor Programming 96 Bakery Y232K Bug class Bakery … public void flag[i] = label[i] = implements Lock { lock() { true; max(label[0], …,label[n-1])+1; while ($k flag[k] && (label[i],i) > (label[k],k)); } Art of Multiprocessor Programming 97 Bakery Y232K Bug class Bakery … public void flag[i] = label[i] = Mutex breaks if label[i] overflows implements Lock { lock() { true; max(label[0], …,label[n-1])+1; while ($k flag[k] && (label[i],i) > (label[k],k)); } Art of Multiprocessor Programming 98 Does Overflow Actually Matter? • Yes – Y2K – 18 January 2038 (Unix time_t rollover) – 16-bit counters • No – 64-bit counters • Maybe – 32-bit counters Art of Multiprocessor Programming 99 palavras de cautela • esses algoritmos assumem coisas que achamos intuitivas – instruções em uma thread serão executadas na ordem em que as enxergamos – ... mas compilador pode pensar diferente... Art of Multiprocessor Programming 100 The Flag Example y.read(0) x.write(1) y.write(1) x.read(0) time Art of Multiprocessor Programming 101 The Flag Example y.read(0) x.write(1) y.write(1) x.read(0) • Each thread’s view is sequentially consistent time – It went first Art of Multiprocessor Programming 102 The Flag Example y.read(0) x.write(1) y.write(1) x.read(0) • Entire history isn’t sequentially consistent time – Can’t both go first Art of Multiprocessor Programming 103 The Flag Example y.read(0) x.write(1) y.write(1) x.read(0) • Is this behavior really so wrong? – We can argue either way … time Art of Multiprocessor Programming 104 Opinion1: It’s Wrong • This pattern – Write mine, read yours • Is exactly the flag principle – Beloved of Alice and Bob – Heart of mutual exclusion • Peterson • Bakery, etc. • It’s non-negotiable! Art of Multiprocessor Programming 105 Opinion2: But It Feels So Right … • Many hardware architects think that sequential consistency is too strong • Too expensive to implement in modern hardware • OK if flag principle – violated by default – Honored by explicit request Art of Multiprocessor Programming 106 This work is licensed under a Creative Commons AttributionShareAlike 2.5 License. • You are free: – to Share — to copy, distribute and transmit the work – to Remix — to adapt the work • Under the following conditions: – Attribution. You must attribute the work to “The Art of Multiprocessor Programming” (but not in any way that suggests that the authors endorse you or your use of the work). – Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license. • For any reuse or distribution, you must make clear to others the license terms of this work. The best way to do this is with a link to – http://creativecommons.org/licenses/by-sa/3.0/. • Any of the above conditions can be waived if you get permission from the copyright holder. • Nothing in this license impairs or restricts the author's moral rights. Art of Multiprocessor Programming 107