Universality of Consensus and The Nature of Progress Companion slides for The Art of Multiprocessor Programming by Maurice Herlihy & Nir Shavit Turing Computability 1 0 1 1 0 1 0 • A mathematical model of computation • Computable = Computable on a T-Machine Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 2 Shared-Memory Computability cache cache cache shared memory • Model of asynchronous concurrent computation • Computable = Wait-free/Lock-free computable on aMultiprocessor multiprocessor Art of Programming© Copyright Herlihy-Shavit 2007 3 Can we implement them from The Consensus Hierarchy any other object that has consensus number ∞? 1 Read/Write Like Registers, Snapshots… compareAndSet()… 2 getAndSet, getAndIncrement, … FIFO Queue, LIFO Stack . . . ∞ Multiple Assignment compareAndSet,… Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 4 Theorem: Universality • Consensus is universal • From n-thread consensus build a – – – – Wait-free Linearizable n-threaded implementation Of any sequentially specified object Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 5 Proof Outline • A universal construction – From n-consensus objects – And atomic registers • Any wait-free linearizable object – Not a practical construction – But we know where to start looking … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 6 Like a Turing Machine • This construction – Illustrates what needs to be done – Optimization fodder • Correctness, not efficiency – Why does it work? (Asks the scientist) – How does it work? (Asks the engineer) – Would you like fries with that? (Asks the liberal arts major) Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 7 A Generic Sequential Object public interface SeqObject { public abstract Response apply(Invocation invoc); } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 8 A Generic Sequential Object public interface SeqObject { public abstract Response apply(Invocation invoc); } Push:5, Pop:null Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 9 Invocation public class Invoc { public String method; public Object[] args; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 10 Invocation public class Invoc { public String method; public Object[] args; } Method name Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 11 Invocation public class Invoc { public String method; public Object[] args; } Arguments Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 12 A Generic Sequential Object public interface SeqObject { public abstract Response apply(Invocation invoc); } OK, 4 Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 13 Response public class Response { public Object value; } Return value Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 14 A Universal Concurrent Object public interface SeqObject { public abstract Response apply(Invocation invoc); } A universal concurrent object is a concurrent object that is linearizable to the generic sequential object Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 15 Start with Lock-Free Universal Construction • First Lock-free: infinitely often some method call finishes. • Then Wait-Free: each method call takes a finite number of steps to finish Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 16 Lock-free Construction: Naïve Idea • Use consensus object to store pointer to cell with current state • Each thread creates new cell – computes outcome, – and tries to switch pointer to its outcome • Unfortunately not… – consensus objects can be used once only Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 17 Naïve Idea deq enq Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 18 Naïve Idea Concurrent Object enq Decide which to apply using consensus head deq ? Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 19 Why only once? Why is consensus object not readable? Queue based consensus public decide(object value) { propose(value); Ball ball = this.queue.deq(); if (ball == Ball.RED) return proposed[i]; else return proposed[1-i]; } Solved one time 2-consensus. Not clear how to allow reuse of object or reading its state… (1) Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 20 Improved Idea: Linked-List Representation Each node contains a fresh consensus object used to decide on next operation deq tail enq enq Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 enq 21 Universal Construction • Object represented as – Initial Object State – A Log: a linked list of the method calls • New method call – Find end of list – Atomically append call – Compute response by traversing the log upto the call Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 22 Basic Idea • Use one-time consensus object to decide next pointer • All threads update actual next pointer based on decision – OK because they all write the same value • Challenges – Lock-free means we need to worry what happens if a thread stops in the middle Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 23 Basic Data Structures public class Node implements java.lang.Comparable { public Invoc invoc; public Consensus<Node> decideNext; public Node next; public int seq; public Node(Invoc invoc) { invoc = invoc; decideNext = new Consensus<Node>() seq = 0; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 24 Basic Data Structures public class Node implements java.lang.Comparable { public Invoc invoc; public Consensus<Node> decideNext; public Node next; public int seq; public Node(Invoc invoc) Standard interface for {class whose invoc = invoc; objects are totally ordered decideNext = new Consensus<Node>() seq = 0; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 25 Basic Data Structures public class Node implements java.lang.Comparable { public Invoc invoc; public Consensus<Node> decideNext; public Node next; public int seq; public Node(Invoc invoc) { the invocation invoc = invoc; decideNext = new Consensus<Node>() seq = 0; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 26 Basic Data Structures public class Node implements java.lang.Comparable { public Invoc invoc; public Consensus<Node> decideNext; public Node next; public int seq; public Node(Invoc invoc) { invoc = invoc; Decide on next node decideNext = new Consensus<Node>() (next method applied to object) seq = 0; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 27 Basic Data Structures public class Node implements java.lang.Comparable { public Invoc invoc; public Consensus<Node> decideNext; public Node next; public int seq; public Node(Invoc invoc) { invoc = invoc; Traversable to next node decideNext =pointer new Consensus<Node>() because you cannot seq(needed = 0; }repeatedly read a consensus object) Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 28 Basic Data Structures public class Node implements java.lang.Comparable { public Invoc invoc; public Consensus<Node> decideNext; public Node next; public int seq; public Node(Invoc invoc) { invoc = invoc; number decideNext Seq = new Consensus<Node>() seq = 0; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 29 Basic Data Structures Create a new node for a given public class Node implements method invocation java.lang.Comparable { public Invoc invoc; public Consensus<Node> decideNext; public Node next; public int seq; public Node(Invoc invoc) { invoc = invoc; decideNext = new Consensus<Node>() seq = 0; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 30 Universal Object Seq number, Invoc next node tail 1 2 3 4 decideNext (Consensus Object) head Ptr to cell w/highest Seq Num Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 31 Universal Object node tail 1 2 3 4 head Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 All threads repeatedly modify head…back to where we started? 32 The Solution Threads find head by finding Max of nodes pointed to by head array node tail 1 2 3 4 … head i Make head an array Thread i updates i Art location of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 Ptr to node at front 33 Universal Object public class Universal { private Node[] head; private Node tail = new Node(); tail.seq = 1; for (int j=0; j < n; j++){ head[j] = tail } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 34 Universal Object public class Universal { private Node[] head; private Node tail = new Node(); tail.seq = 1; for (int j=0; j < n; j++){ head[j] = tail } Head Pointers Array Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 35 Universal Object public class Universal { private Node[] head; private Node tail = new Node(); tail.seq = 1; for (int j=0; j < n; j++){ head[j] = tail } Tail is a sentinel node with sequence number 1 Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 36 Universal Object public class Universal { private Node[] head; private Node tail = new Node(); tail.seq = 1; for (int j=0; j < n; j++){ head[j] = tail } Tail is a sentinel node with sequence number 1 Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 37 Universal Object public class Universal { private Node[] head; private Node tail = new Node(); tail.seq = 1; for (int j=0; j < n; j++){ head[j] = tail } Initially head points to tail Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 38 Find Max Head Value public static Node max(Node[] array) { Node max = array[0]; for (int i = 1; i < array.length; i++) if (max.seq < array[i].seq) max = array[i]; return max; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 39 Find Max Head Value public static Node max(Node[] array) { Node max = array[0]; for (int i = 0; i < array.length; i++) if (max.seq < array[i].seq) max = array[i]; return max; } Traverse the array Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 40 Find Max Head Value public static Node max(Node[] array) { Node max = array[0]; for (int i = 0; i < array.length; i++) if (max.seq < array[i].seq) max = array[i]; return max; } Compare the seq nums of nodes pointed to by the array Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 41 Find Max Head Value public static Node max(Node[] array) { Node max = array[0]; for (int i = 0; i < array.length; i++) if (max.seq < array[i].seq) max = array[i]; return max; } Return the node with max seq num Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 42 Universal Application Part I public Response apply(Invoc invoc) { int i = ThreadID.get(); Node prefer = new node(invoc); while (prefer.seq == 0) { Node before = Node.max(head); Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; head[i] = after; } … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 43 Universal Application Part I public Response apply(Invoc invoc) { int i = ThreadID.get(); Node prefer = new node(invoc); while (prefer.seq == 0) { Node before = Node.max(head); Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; head[i] = have after; Apply will invocation as input } return the appropriate response and … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 44 Universal Application Part I public Response apply(Invoc invoc) { int i = ThreadID.get(); Node prefer = new node(invoc); while (prefer.seq == 0) { Node before = Node.max(head); Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; head[i]My = after; id } … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 45 Universal Application Part I public Response apply(Invoc invoc) { int i = ThreadID.get(); Node prefer = new node(invoc); while (prefer.seq == 0) { Node before = Node.max(head); Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; head[i] = after; } My method call … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 46 Universal Application Part I public Response apply(Invoc invoc) { int i = ThreadID.get(); Node prefer = new node(invoc); while (prefer.seq == 0) { Node before = Node.max(head); Node after = before.decideNext.decide(prefer); As long as I before.next = after; after.seq = before.seq 1; been have +not head[i] = after; threaded into } list … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 47 Universal Application Part I public Response apply(Invoc invoc) { int i = ThreadID.get(); Node prefer = new node(invoc); while (prefer.seq == 0) { Node before = Node.max(head); Node after = before.decideNext.decide(prefer); before.next = after; Node at head of after.seq = before.seq + 1;I will list that head[i] = after; try to append to } … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 48 Universal Application Part I public Response apply(Invoc invoc) { int i = ThreadID.get(); Node prefer = new node(invoc); while (prefer.seq == 0) { Node before = Node.max(head); Node after = before.decideNext.decide(prefer); before.next = after; Decide winning after.seq = before.seq + 1; head[i] = after; node; could have } already been } decided Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 49 Universal Application public Response apply(Invoc invoc) { int i = ThreadID.get(); Node prefer = new node(invoc); while (prefer.seq == 0) { Node before = Node.max(head); Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; head[i] = after; Could have already Set next pointer } been set by winner…in based on decision } which case no affect Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 50 Universal Application Part I public Response apply(Invoc invoc) { int i = ThreadID.get(); Node prefer = new node(invoc); while (prefer.seq == 0) { Node before = Node.max(head); Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; head[i] = after; Set seq number } … indicating node was appended Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 51 Universal Application Part I public Response apply(Invoc invoc) { int i = ThreadID.get(); Node prefer = new node(invoc); while (prefer.seq == 0) { Node before = Node.max(head); Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; add to head head[i] = after; array so new } … head will be Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 found 52 Part II – Compute Response Red’s method call tail null enq( ) Private copy of object enq( ) deq() … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 Return 53 Universal Application Part II ... //compute my response SeqObject MyObject = new SeqObject(); current = tail.next; while (current != prefer){ MyObject.apply(current.invoc); current = current.next; } return MyObject.apply(current.invoc); } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 54 Universal Application Part II ... //compute my response SeqObject MyObject = new SeqObject(); current = tail.next; while (current != prefer){ MyObject.apply(current.invoc); current = current.next; } Compute the result by return MyObject.apply(current.invoc); } sequentially applying the method calls in the list to a private copy of the object Art of Multiprocessor Programming© Copyright starting from the initial state Herlihy-Shavit 2007 55 Universal Application Part II ... //compute my response SeqObject MyObject = new SeqObject(); current = tail.next; while (current != prefer){ MyObject.apply(current.invoc); current = current.next; } return MyObject.apply(current.invoc); } Start with initialized copy of the sequential object Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 56 Universal Application Part II ... //compute my response SeqObject MyObject = new SeqObject(); current = tail.next; while (current != prefer){ MyObject.apply(current.invoc); current = current.next; } return MyObject.apply(current.invoc); } First new method call is appended after the tail Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 57 Universal Application Part II ... //compute my response SeqObject MyObject = new SeqObject(); current = tail.next; while (current != prefer){ MyObject.apply(current.invoc); current = current.next; } return MyObject.apply(current.invoc); } While not reached my own Art ofmethod Multiprocessor call Programming© Copyright Herlihy-Shavit 2007 58 Universal Application Part II ... //compute my response SeqObject MyObject = new SeqObject(); current = tail.next; while (current != prefer){ MyObject.apply(current.invoc); current = current.next; } return MyObject.apply(current.invoc); } Apply the current nodes Art of Multiprocessor method to object Programming© Copyright Herlihy-Shavit 2007 59 Universal Application Part II ... //compute my response SeqObject MyObject = new SeqObject(); current = tail.next; while (current != prefer){ MyObject.apply(current.invoc); current = current.next; } return MyObject.apply(current.invoc); } Return the result after Art ofmy Multiprocessor applying own method call Programming© Copyright Herlihy-Shavit 2007 60 Correctness • List defines linearized sequential history • Thread returns its response based on list order Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 61 Lock-freedom • Lock-free because – A thread moves forward in list – Can repeatedly fail to win consensus on “real” head only if another succeeds – Consensus winner adds node and completes within a finite number of steps Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 62 Wait-free Construction • Lock-free construction + announce array • Stores (pointer to) node in announce – If a thread doesn’t append its node – Another thread will see it in array and help append it Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 63 Helping • “Announcing” my intention – Guarantees progress – Even if the scheduler hates me – My method call will complete • Makes protocol wait-free • Otherwise starvation possible Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 64 Wait-free Construction announce … i tail 1 Ptr to the cell thread i wants to append 2 3 4 … head i Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 65 The Announce Array public class Universal { private Node[] announce; private Node[] head; private Node tail = new node(); tail.seq = 1; for (int j=0; j < n; j++){ head[j] = tail; announce[j] = tail }; Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 66 The Announce Array public class Universal { private Node[] announce; private Node[] head; private Node tail = new node(); tail.seq = 1; for (int j=0; j < n; j++){ head[j] = tail; announce[j] = tail }; Announce array Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 67 The Announce Array public class Universal { private Node[] announce; private Node[] head; private Node tail = new node(); tail.seq = 1; for (int j=0; j < n; j++){ head[j] = tail; announce[j] = tail }; All entries initially point to tail Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 68 A Cry For Help public Response apply(Invoc invoc) { int i = ThreadID.get(); announce[i] = new Node(invoc); head[i] = Node.max(head); while (announce[i].seq == 0) { … // while node not appended to list … } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 69 A Cry For Help public Response apply(Invoc invoc) { int i = ThreadID.get(); announce[i] = new Node(invoc); head[i] = Node.max(head); while (announce[i].seq == 0) { … // while node not appended to list … } Announce new method call (node), asking help from others Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 70 A Cry For Help public Response apply(Invoc invoc) { int i = ThreadID.get(); announce[i] = new Node(invoc); head[i] = Node.max(head); while (announce[i].seq == 0) { … // while node not appended to list … } Look for end of list Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 71 A Cry For Help public Response apply(Invoc invoc) { int i = ThreadID.get(); announce[i] = new Node(invoc); head[i] = Node.max(head); while (announce[i].seq == 0) { … // while node not appended to list … } Main loop, while node not appended (either by me or some thread helping me) Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 72 Main Loop • Non-zero sequence number indicates success • Thread keeps helping append nodes • Until its own node is appended Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 73 Main Loop while (announce[i].seq == 0) { Node before = head[i]; Node help = announce[(before.seq + 1 % n)]; if (help.seq == 0) prefer = help; else prefer = announce[i]; … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 74 Main Loop while (announce[i].seq == 0) { Node before = head[i]; Node help = announce[(before.seq + 1 % n)]; if (help.seq == 0) prefer = help; else prefer announce[i]; Keep trying= until my cell gets a … sequence number Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 75 Main Loop while (announce[i].seq == 0) { Node before = head[i]; Node help = announce[(before.seq + 1 % n)]; if (help.seq == 0) prefer = help; else prefer = announce[i]; Possible end of list … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 76 Main Loop while (announce[i].seq == 0) { Node before = head[i]; Node help = announce[(before.seq + 1 % n)]; if (help.seq == 0) prefer = help; else prefer = announce[i]; … Who do I help? Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 77 Altruism • Choose a thread to “help” • If that thread needs help – Try to append its node – Otherwise append your own • Worst case – Everyone tries to help same pitiful loser – Someone succeeds Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 78 Help! • When last node in list has sequence number k • All threads check … – Whether thread k+1 mod n wants help – If so, try to append her node first Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 79 Help! • First time after thread k+1 announces – No guarantees • After n more nodes appended – Everyone sees that thread k+1 wants help – Everyone tries to append that node – Someone succeeds Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 80 Sliding Window Lemma • After thread A announces its node • No more than n other calls – Can start and finish – Without appending A’s node Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 81 Helping So all see and help append 4 Thread 4: Help me! announce Max head +1 = N+4 4 tail 1 2 3 … N+2 N+3 head Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 82 The Sliding Help Window announce 3 4 tail 1 2 3 … Help 3Help 4 N+2 N+3 head Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 83 Sliding Help Window while (announce[i].seq == 0) { Node before = head[i]; Node help = announce[(before.seq + 1 % n)]; if (help.seq == 0) prefer = help; else prefer = announce[i]; … In each main loop iteration pick another thread to help Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 84 Sliding Help Window Help if help required, but otherwise==it’s while (announce[i].seq 0)all { about me! Node before = head[i]; Node help = announce[(before.seq + 1 % n)]; if (help.seq == 0) prefer = help; else prefer = announce[i]; … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 85 Rest is Same as Lock-free while (prefer.seq == 0) { … Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; head[i] = after; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 86 Rest is Same as Lock-free while (prefer.seq == 0) { … Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; head[i] = after; } Decide next node to be appended Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 87 Rest is Same as Lock-free while (prefer.seq == 0) { … Update next based on decision Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; head[i] = after; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 88 Rest is Same as Lock-free while (prefer.seq == 0) { … Tell world that node is appended Node after = before.decideNext.decide(prefer); before.next = after; after.seq = before.seq + 1; head[i] = after; } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 89 Finishing the Job • Once thread’s node is linked • The rest is again the same as in lockfree alg • Compute the result by sequentially applying the method calls in the list to a private copy of the object starting from the initial state Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 90 Then Same Part II ... //compute my response SeqObject MyObject = new SeqObject(); current = tail.next; while (current != prefer){ MyObject.apply(current.invoc); current = current.next; } return MyObject.apply(current.invoc); } Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 91 Universal Application Part II ... //compute my response SeqObject MyObject = new SeqObject(); current = tail.next; while (current != prefer){ MyObject.apply(current.invoc); current = current.next; } return MyObject.apply(current.invoc); } Return the result after Art ofmy Multiprocessor applying own method call Programming© Copyright Herlihy-Shavit 2007 92 Shared-Memory Computability Universal 10011 Object Wait-free/Lock-free computable = Threads with methods that solve nconsensus Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 93 GetAndSet is not Universal public class RMWRegister { private int value; public boolean getAndSet(int update) { int prior = this.value; this.value = update; return prior; } } (1) Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 94 GetAndSet is not Universal public class RMWRegister { private int value; public boolean getAndSet(int update) { int prior = this.value; this.value = update; return prior; } Consensus number 2 } (1) Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 95 GetAndSet is not Universal public class RMWRegister { private int value; public boolean getAndSet(int update) { int prior = this.value; this.value = update; return prior; } Not universal for ≥ 3 threads } (1) Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 96 CompareAndSet is Universal public class RMWRegister { private int value; public boolean compareAndSet(int expected, int update) { int prior = this.value; if (this.value == expected) { this.value = update; return true; } return false; }} (1) Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 97 CompareAndSet is Universal public class RMWRegister { private int value; public boolean compareAndSet(int expected, int update) { int prior = this.value; if (this.value == expected) { this.value = update; return true; } return false; Consensus number ∞ }} (1) Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 98 CompareAndSet is Universal public class RMWRegister { private int value; public boolean compareAndSet(int expected, int update) { int prior = this.value; if (this.value == expected) { this.value = update; return true; } return false; Universal for any number of threads }} (1) Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 99 On Older Architectures • IBM 360 – testAndSet (getAndSet) • NYU UltraComputer – getAndAdd • Neither universal – Except for 2 threads Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 100 On Newer Architectures • Intel x86, Itanium, SPARC – compareAndSet • Alpha AXP, PowerPC – Load-locked/store-conditional • All universal – For any number of threads • Trend is clear … Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 101 Practical Implications • Any architecture that does not provide a universal primitive has inherent limitations • You cannot avoid locking for concurrent data structures … • But why do we care? Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 102 Locking and Schedeuling • What are the practical implications of locking? • Locking affects the assumptions we need to make on the operating system in order to guarantee progress • Lets understand how… Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 103 Schedeuling • The scheduler is a part of the OS that determines – Which thread gets to run on which processor – How long it runs for • A given thread can thus be active, that is, executing instructions, or suspended Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 104 Review Progress Conditions • Deadlock-free: some thread trying to acquire the locks eventually succeeds. • Starvation-free: every thread trying to acquire the locks eventually succeeds. • Lock-free: some thread calling the method eventually returns. • Wait-free: every thread calling the method eventually returns. • Obstruction-free: every thread calling the method returns if it executes in isolation for long enough. 105 The Simple Snapshot is Obstruction-Free • Put increasing labels on each entry • Collect twice • If both agree, – We’re done • Otherwise, – Try again Collect2 Collect1 1 1 22 1 22 1 7 13 18 12 © 2007 Herlihy & Shavit = 7 13 18 12 106 Obstruction-freedom • In the simple snapshot alg: • The update method is wait-free • But the scan is obstruction-free: will complete only if it executes for long enough without concurrent updates. Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 107 Progress of Methods • Some of the above defs refer to locks (part of implementation) or method calls • And they ignore the scheduler • Lets refine our progress definitions so that they apply to methods, and • Take scheduling into account Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 108 A “Periodic Table” of Progress Conditions Non-Blocking Everyone makes progress Waitfree Someone makes progress Lockfree Obstructionfree Blocking Starvationfree Deadlockfree 109 A bit more formally • Standard notion of abstract object • Progress conditions relate to method calls of an object • Threads on a multiprocessor never fail: – A thread is active if it takes an infinite number of concrete (machine level) steps – And is suspended if not. 110 Maximal vs. Minimal • For a given history H: • Minimal progress: in every suffix of H, some method call eventually completes. • Maximal progress: in every suffix of H, every method call eventually completes. 111 The “Periodic Table” of Progress Conditions Non-Blocking Maximal progress Minimal progress Waitfree Lockfree Blocking Obstruction- Starvationfree free Deadlockfree 112 The Scheduler’s Role On a multiprocessor progress properties: • Are not about the guarantees a method's implementation provides. • They are about the scheduling assumptions needed in order to provide minimal or maximal progress. 113 Fair Scheduling • A history is fair if each thread takes an infinite number of steps • A method implementation is deadlock-free if it guarantees minimal progress in every fair history, and maximal progress in some fair history. 114 Starvation Freedom • A method implementation is starvation-free if it guarantees maximal progress in every fair history. 115 Dependent Progress • A progress condition is dependent if it does not guarantee minimal progress in every history, and is independent if it does. • The blocking progress conditions (deadlock-freedom, Starvationfreedom) are dependent 116 Non-blocking Independent Conditions • A method implementation is lockfree if it guarantees minimal progress in every history, and maximal progress in some history. • A method implementation is waitfree if it guarantees maximal progress in every history. 117 The “Periodic Table” of Progress Conditions Non-Blocking Maximal progress Minimal progress Waitfree Blocking Obstruction- Starvationfree free Lockfree Deadlockfree Independent Dependent 118 Uniformly Isolating Schedules • A history is uniformly isolating if, for every k > 0, any thread that takes an infinite number of steps has an interval where it takes at least k contiguous steps • Modern systems provide ways of providing isolation…later we will learn about “backoff” and “yeild”. 119 A Non-blocking Dependent Condition • A method implementation is obstruction-free if it guarantees maximal progress in every uniformly isolating history. 120 The “Periodic Table” of Progress Conditions Non-Blocking Blocking Maximal progress Uniform iso Obstruction- StarvationWait-scheduler Fair scheduler free free free Minimal progress Lockfree Independent DeadlockFair scheduler free Dependent 121 The “Periodic Table” of Progress Conditions Blocking Non-Blocking Maximal progress Waitfree Minimal progress Lockfree Independent Obstruction- Starvationfree free ? Clashfree Deadlockfree Dependent 122 Clash-Freedom: the “Einsteinium” of Progress • A method implementation is clash-free if it guarantees minimal progress in every uniformly isolating history. • Thm: clash-freedom strictly weaker than obstruction-freedom 123 Getting from Minimal to Maximal Blocking Non-Blocking Maximal progress Minimal progress Waitfree Lockfree Independent Obstruction- Starvationfree free ? ? Clashfree Deadlock- Helping free Dependent 124 Universal Constructions • Our lock-free universal construction provides minimal progress • A scheduler is benevolent for that algorithm if it guarantees maximal progress in every allowable history. • Many real-world operating system schedulers are benevolent • They do not single out any indiviudual thread 125 Getting from Minimal to Universal Lock-free Construction Maximal Universal Wait-free Non-Blocking Maximal progress Minimal progress Waitfree Construction Blocking Obstruction- Starvationfree free ? ? ClashLockDeadlock- Helping free free Use Wait-free/Lock-free free Consensus Objects Independent Dependent 126 Getting from Construction Minimal Universal Wait-free Maximal Non-Blocking to Blocking Universal Lock-free Maximal Construction Obstruction- StarvationWaitprogress free Minimal progress Lockfree free free ? Clashfree Deadlockfree If we use Starvation-free/Deadlock-free Consensus Objects result Dependent is respectively Independent 127 Starvation-free/Deadlock-free Benevolent Schedulers • Consider an algorithm that guarantees minimal progress. • A scheduler is benevolent for that algorithm if it guarantees maximal progress in every allowable history. • Many real-world operating system schedulers are benevolent • They do not single out any indiviudual thread 128 In Practice On a multiprocessor we will write code expecting maximal progress. Progress conditions will then define the scheduling assumptions needed in order to provide it. s129 This Means We will mostly write lock-free and lock-based deadlock-free algorithms… and expect them to behave as if they are wait-free… because modern schedulers can be made benevolent and fair. s130 Principles to Practice • We learned how to define the safety (correctness) and liveness (progress) of concurrent programs and objects • We are ready to start the practice of implementing them • Next lecture: implementing spin locks on multiprocesor machines… Art of Multiprocessor Programming© Copyright Herlihy-Shavit 2007 131 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© Copyright Herlihy-Shavit 2007 132