Concurrent Linked Lists and Linearizability Proofs Companion slides for The Art of Multiprocessor Programming by Maurice Herlihy & Nir Shavit Modified by Pavol Černý, Programming Paradigms for Concurrency, Fall 2010, IST Austria
Art of Multiprocessor Programming2 First: Issues in Linearizability Q1: Are linearizability proofs composable? Q2: Linearizability vs (strict) serializability
Art of Multiprocessor Programming 3 Linearizability History H is linearizable if it can be extended to G by –Appending zero or more responses to pending invocations –Discarding other pending invocations So that G is equivalent to –Legal sequential history S –where G S
Art of Multiprocessor Programming 4 Linearizability Equivalently: Each method should –take effect instantaneously –between invocation and response events –object is correct if this “ sequential ” behavior is correct
Art of Multiprocessor Programming 5 Composability Theorem History H is linearizable if and only if –For every object x –H|x is linearizable (proof on the board)
Art of Multiprocessor Programming 6 Linearizability vs (strict) serializability Serializability: A history is serializable if it is equivalent to one in which transactions appear to execute sequentially (without interleaving) Strict serializability: In addition, the order of transactions in the sequential history respects precedence order from the interleaved history Linearizability: A special case of strict serializability, where each “transaction” (method) accesses a single object (Strict) serializability non-local.
Art of Multiprocessor Programming7 Today: Concurrent Objects Adding threads should not lower throughput –Contention effects Should increase throughput –Not possible if inherently sequential –Surprising things are parallelizable
Art of Multiprocessor Programming8 Coarse-Grained Synchronization Each method locks the object –Avoid contention using queue locks
Art of Multiprocessor Programming9 Coarse-Grained Synchronization Each method locks the object –Avoid contention using queue locks –Easy to reason about In simple cases
Art of Multiprocessor Programming10 Coarse-Grained Synchronization Each method locks the object –Avoid contention using queue locks –Easy to reason about In simple cases So, are we done?
Art of Multiprocessor Programming11 Coarse-Grained Synchronization Sequential bottleneck –Threads “stand in line”
Art of Multiprocessor Programming12 Coarse-Grained Synchronization Sequential bottleneck –Threads “stand in line” Adding more threads –Does not improve throughput –Struggle to keep it from getting worse
Art of Multiprocessor Programming13 Coarse-Grained Synchronization Sequential bottleneck –Threads “stand in line” Adding more threads –Does not improve throughput –Struggle to keep it from getting worse So why even use a multiprocessor? –Well, some apps inherently parallel …
Art of Multiprocessor Programming14 Fine-Grained Synchronization Instead of using a single lock … Split object into –Independently-synchronized components Methods conflict when they access –The same component … –At the same time For us: example for proofs of linearizability
Art of Multiprocessor Programming15 Set Interface Unordered collection of items No duplicates Methods –add(x) put x in set –remove(x) take x out of set –contains(x) tests if x in set
Art of Multiprocessor Programming16 List-Based Sets public interface Set { public boolean add(T x); public boolean remove(T x); public boolean contains(T x); }
Art of Multiprocessor Programming17 List Node public class Node { public T item; public int key; public Node next; }
Art of Multiprocessor Programming18 List Node public class Node { public T item; public int key; public Node next; } item of interest
Art of Multiprocessor Programming19 List Node public class Node { public T item; public int key; public Node next; } Usually hash code
Art of Multiprocessor Programming20 List Node public class Node { public T item; public int key; public Node next; } Reference to next node
Art of Multiprocessor Programming21 The List-Based Set abc Sorted with Sentinel nodes (min & max possible keys) -∞ +∞
Art of Multiprocessor Programming22 Invariants Sentinel nodes –tail reachable from head Sorted No duplicates
Art of Multiprocessor Programming23 Sequential List Based Set a c d a b c Add() Remove()
Art of Multiprocessor Programming24 Sequential List Based Set a c d b a b c Add() Remove()
Art of Multiprocessor Programming25 Coarse Grained Locking a b d
Art of Multiprocessor Programming26 Coarse Grained Locking a b d c
Art of Multiprocessor Programming27 honk! Coarse Grained Locking a b d c Simple but hotspot + bottleneck honk!
Art of Multiprocessor Programming28 Coarse-Grained Locking Easy, same as synchronized methods –“One lock to rule them all …”
Art of Multiprocessor Programming29 Coarse-Grained Locking Easy, same as synchronized methods –“One lock to rule them all …” Simple, clearly correct –Deserves respect! Works poorly with contention –Queue locks help –But bottleneck still an issue
Art of Multiprocessor Programming30 Fine-grained Locking Requires careful thought Split object into pieces –Each piece has own lock –Methods that work on disjoint pieces need not exclude each other
Art of Multiprocessor Programming31 Hand-over-Hand locking abc
Art of Multiprocessor Programming32 Hand-over-Hand locking abc
Art of Multiprocessor Programming33 Hand-over-Hand locking abc
Art of Multiprocessor Programming34 Hand-over-Hand locking abc
Art of Multiprocessor Programming35 Hand-over-Hand locking abc
Art of Multiprocessor Programming36 Removing a Node abcd remove(b)
Art of Multiprocessor Programming37 Removing a Node abcd remove(b)
Art of Multiprocessor Programming38 Removing a Node abcd remove(b)
Art of Multiprocessor Programming39 Removing a Node abcd remove(b)
Art of Multiprocessor Programming40 Removing a Node abcd remove(b)
Art of Multiprocessor Programming41 Removing a Node acd remove(b) Why hold 2 locks?
Art of Multiprocessor Programming42 Remove method public boolean remove(Item item) { int key = item.hashCode(); Node pred, curr; try { … } finally { curr.unlock(); pred.unlock(); }}
Art of Multiprocessor Programming43 Remove method public boolean remove(Item item) { int key = item.hashCode(); Node pred, curr; try { … } finally { curr.unlock(); pred.unlock(); }} Key used to order node
Art of Multiprocessor Programming44 Remove method public boolean remove(Item item) { int key = item.hashCode(); Node pred, curr; try { … } finally { currNode.unlock(); predNode.unlock(); }} Predecessor and current nodes
Art of Multiprocessor Programming45 Remove method public boolean remove(Item item) { int key = item.hashCode(); Node pred, curr; try { … } finally { curr.unlock(); pred.unlock(); }} Make sure locks released
Art of Multiprocessor Programming46 Remove method public boolean remove(Item item) { int key = item.hashCode(); Node pred, curr; try { … } finally { curr.unlock(); pred.unlock(); }} Everything else
Art of Multiprocessor Programming47 Remove method try { pred = this.head; pred.lock(); curr = pred.next; curr.lock(); … } finally { … }
Art of Multiprocessor Programming48 Remove method try { pred = this.head; pred.lock(); curr = pred.next; curr.lock(); … } finally { … } lock pred == head
Art of Multiprocessor Programming49 Remove method try { pred = this.head; pred.lock(); curr = pred.next; curr.lock(); … } finally { … } Lock current
Art of Multiprocessor Programming50 Remove method try { pred = this.head; pred.lock(); curr = pred.next; curr.lock(); … } finally { … } Traversing list
Art of Multiprocessor Programming51 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } return false;
Art of Multiprocessor Programming52 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } return false; Search key range
Art of Multiprocessor Programming53 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } return false; At start of each loop: curr and pred locked
Art of Multiprocessor Programming54 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } return false; If item found, remove node
Art of Multiprocessor Programming55 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } return false; If node found, remove it
Art of Multiprocessor Programming56 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } return false; Unlock predecessor
Art of Multiprocessor Programming57 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } return false; Only one node locked!
Art of Multiprocessor Programming58 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } return false; demote current
Art of Multiprocessor Programming59 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = currNode; curr = curr.next; curr.lock(); } return false; Find and lock new current
Art of Multiprocessor Programming60 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = currNode; curr = curr.next; curr.lock(); } return false; Lock invariant restored
Art of Multiprocessor Programming61 Remove: searching while (curr.key <= key) { if (item == curr.item) { pred.next = curr.next; return true; } pred.unlock(); pred = curr; curr = curr.next; curr.lock(); } return false; Otherwise, not present
Art of Multiprocessor Programming62 Adding Nodes To add node e –Must lock predecessor –Must lock successor Neither can be deleted
Proving linearizability Rely-guarantee approach –Vafeiadis, Herlihy, Hoare, Shapiro 2006 Reduction approach –Elmas, Qadeer, Tasiran 2009 Art of Multiprocessor Programming63
Rely-guarantee Sequential programs: {p}C{q} -- Hoare logic Parallel programs: C models (p,R,G,q) Art of Multiprocessor Programming64
Rely-guarantee Parallel programs: C models (p,R,G,q) p – precondition – state predicate q – postcondition – transition predicate R – rely conditions – what we tolearte from other threads G – guarantee conditions – we do our part Art of Multiprocessor Programming65
Rely-guarantee Rules: {p}C{q} in Hoare logic models (p,R,G,q) Art of Multiprocessor Programming66 C1 models (p1,R,G,q1) C2 models (p2,R,G,q2) q1 -> p2 C1;C2 models (p1,R,G,(q1;R*;q2))
Rely-guarantee Art of Multiprocessor Programming67 C1 models (p1,R1,G1,q1) G2 -> R1 C2 models (p2,R2,G2,q2) G1 -> R2 C1 || C2 models (p1&p2,R1&R2,G1 or G2,q) where q is q1;(R1&R2)*;q2 or q1;(R1&R2)*;q2
68 Example: increment local int t; acquire (lock); t := x; t := t + 1; x := t; release (lock); global int x; x := 0; || assert (x == 2); local int t; acquire (lock); t := x; t := t + 1; x := t; release (lock);
69 Example: increment Rely condition R: L.owner = self -> x’=x Guarantee condition G: L.owner ≠ self -> x’=x We prove: P models (true,R,G,x’=x+1) P1||P2 models (true,R,G,x’=x+2) (proofs on the board)
Proving remove Art of Multiprocessor Programming70 AbsRemove(e): { AbsResult = (e in Abs); Abs = Abs – {e}} Strategy: 1. find linearization points 2. insert AbsRemove 3. prove that AbsResult = Result
71 Reduction proofs inc (): local int t; acquire (lock); t := x; t := t + 1; x := t; release(lock); Main: x := 0; inc(); || inc(); assert (x == 2); Slides adapted from Tayfun Elmas.
Art of Multiprocessor Programming72 Proof by reduction inc (): int t; acquire (lock); t := x; t := t + 1; x := t; release(lock); R B B B L inc (): int t; acquire (lock); t := x; t := t + 1; x := t; release(lock); inc (): x := x + 1; havoc t; REDUCE-SEQUENTIAL ABSTRACT
73 Reduction ; ; ... 1 2 ... n ... right-mover: For each execution: Exist equivalent executions: 1 2 ... n 1 2 ... n 1 2 ... n ... ;
Art of Multiprocessor Programming 74 Static mover check - 1 Right mover: Commutes to the right of any other action run by a different thread Static right-mover check for : For every action in program: (run by different thread) ;; ; abstracted by
Art of Multiprocessor Programming75 Static mover check - 2 Static right-mover check between and : Simple cases Mover check passes: and access different variables Fails: and 1) simultaneously enabled and 2) perform conflicting accesses to a variable 75 ;; ; abstracted by
Abstraction rule: –Replace with if: Art of Multiprocessor Programming76 Abstraction abstracted by 76 Going wrong more often is sound for assertion checking Forall : error s1s1 s1s1 1. If then exists s1s1 2. If then exists s2s2 s1s1 s2s2 or error s1s1 s1s1
Art of Multiprocessor Programming77 Drawbacks of fine-grained locking lists Better than coarse-grained lock –Threads can traverse in parallel Still not ideal –Long chain of acquire/release –Inefficient