The Art of Multiprocessor Programmin Chapter 05 with Japanese comment.
1. The Relative Power of Synchronization Operations Companion slides for The Art of Multiprocessor Programming 第 5 章 by Maurice Herlihy & Nir Shavit 日本語追記 by 根本和郎
2.
3.
4.
5.
6. From Weakest Register SRSW の最もシンプルなケース 1 0 1 Single reader Single writer Safe Boolean register
7. All the way to a Wait-free Implementation of Atomic Snapshots MRMW MRSW SRSW Saf e Regular Atomic M-valued Boolean Snapshot
8.
9. Why is Mutual Exclusion so wrong? シンクロナイズドの排他制御のよくないところは・・・ (2)
50. From a Critical State A か B の決定が先勝ちで決まる状態 c If A goes first, protocol decides 0 If B goes first, protocol decides 1 0-valent 1-valent
51. Reaching Critical State 相手側が決定しないうちはまだ univalent これはだいたいの概念図 C A C A C B c C B univalent univalent univalent univalent 0-valent 1-valent initially bivalent
52.
53.
54.
55.
56. Possible Interactions ? ? ? ? y.write() ? ? ? ? x.write() ? ? ? ? y.read() ? ? ? ? x.read() y.write() x.write() y.read() x.read() A reads x A reads y
57. Some Thread Reads A runs solo, eventually decides 0 B reads x 1 0 A runs solo, eventually decides 1 c States look the same to A Contradiction
58. Possible Interactions ? ? no no y.write() ? ? no no x.write() no no no no y.read() no no no no x.read() y.write() x.write() y.read() x.read()
59. Writing Distinct Registers A writes y B writes x 1 0 c The song is the same A writes y B writes x Contradiction
60. Possible Interactions ? no no no y.write() no ? no no x.write() no no no no y.read() no no no no x.read() y.write() x.write() y.read() x.read()
61. Writing Same Registers States look the same to A A writes x B writes x 1 A runs solo, eventually decides 1 c 0 A runs solo, eventually decides 0 A writes x Contradiction
62. That’s All, Folks! QED no no no no y.write() no no no no x.write() no no no no y.read() no no no no x.read() y.write() x.write() y.read() x.read()
77. Consensus Using FIFO Queue public class QueueConsensus extends ConsensusProtocol { private Queue queue; public QueueConsensus() { queue = new Queue(); queue.enq(Ball.RED); queue.enq(Ball.BLACK); } … }
78. Initialize Queue public class QueueConsensus extends ConsensusProtocol { private Queue queue; public QueueConsensus() { this .queue = new Queue(); this .queue.enq(Ball.RED); this .queue.enq(Ball.BLACK); } … } 8
79. Who Won? public class QueueConsensus extends ConsensusProtocol { private Queue queue; … public decide(object value) { propose(value); Ball ball = this .queue.deq(); if (ball == Ball.RED) return proposed[i]; else return proposed[1-i]; } }
80. Who Won? public class QueueConsensus extends ConsensusProtocol { private Queue queue; … public decide(object value) { propose(value); Ball ball = this .queue.deq(); if (ball == Ball.RED) return proposed[i]; else return proposed[1-ij]; } } Race to dequeue first queue item
81. Who Won? public class QueueConsensus extends ConsensusProtocol { private Queue queue; … public decide(object value) { propose(value); Ball ball = this.queue.deq(); if (ball == Ball.RED) return proposed[i]; else return proposed[1-i]; } } i = ThreadID.get(); I win if I was first
82. Who Won? public class QueueConsensus extends ConsensusProtocol { private Queue queue; … public decide(object value) { propose(value); Ball ball = this.queue.deq(); if (ball == Ball.RED) return proposed[i]; else return proposed[1-i]; } } Other thread wins if I was second
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95. Double Assignment Interface interface Assign2 { public void assign( int i 1 , int v 1 , int i 2 , int v 2 ); public int read( int i); } (4) (4)
96. Double Assignment Interface interface Assign2 { public void assign( int i 1 , int v 1 , int i 2 , int v 2 ); public int read(int i); } (4) (4) Atomically assign value[i 1 ]= v 1 value[i 2 ]= v 2
97. Double Assignment Interface interface Assign2 { public void assign(int i 1 , int v 1 , int i 2 , int v 2 ); public int read( int i); } (4) (4) Return i-th value
100. Thread A wins if A B (1) Thread B moved later
101. Thread A loses if A B (1) Thread B moved earlier 先に書いた方の勝ち
102. Multi-Consensus Code class MultiConsensus extends …{ Assign2 a = new Assign2(3, EMPTY); public Object decide(object value) { a.assign(i, i, i+1, i); int other = a.read((i+2) % 3); if (other==EMPTY||other==a.read(1)) return proposed[i]; else return proposed[j]; ←j が突然出てくる ? }} (4) (4)
103. Multi-Consensus Code class MultiConsensus extends …{ Assign2 a = new Assign2(3, EMPTY); public Object decide(object value) { a.assign(i, i, i+1, i); int other = a.read((i+2) % 3); if (other==EMPTY||other==a.read(1)) return proposed[i]; else return proposed[j]; }} (4) (4) Extends ConsensusProtocol Decide sets j=i-1 and proposes value
104. Multi-Consensus Code class MultiConsensus extends …{ Assign2 a = new Assign2(3, EMPTY); public Object decide(object value) { a.assign(i, i, i+1, i); int other = a.read((i+2) % 3); if (other==EMPTY||other==a.read(1)) return proposed[i]; else return proposed[j]; }} (4) (4) Three slots initialized to EMPTY
105. Multi-Consensus Code class MultiConsensus extends …{ Assign2 a = new Assign2(3, EMPTY); public Object decide(object value) { a.assign(i, i, i+1, i); int other = a.read((i+2) % 3); if (other==EMPTY||other==a.read(1)) return proposed[i]; else return proposed[j]; }} (4) (4) Assign id 0 to entries 0,1 (or id 1 to entries 1,2)
106. Multi-Consensus Code class MultiConsensus extends …{ Assign2 a = new Assign2(3, EMPTY); public Object decide(object value) { a.assign(i, i, i+1, i); int other = a.read((i+2) % 3); if (other==EMPTY||other==a.read(1)) return proposed[i]; else return proposed[j]; }} (4) (4) Read the register my thread didn’t assign
107. Multi-Consensus Code class MultiConsensus extends …{ Assign2 a = new Assign2(3, EMPTY); public Object decide(object value) { a.assign(i, i, i+1, i); int other = a.read((i+2) % 3); if (other==EMPTY ||other==a.read(1)) return proposed[i]; else return proposed[j]; }} (4) (4) Other thread didn’t move, so I win
108. Multi-Consensus Code class MultiConsensus extends …{ Assign2 a = new Assign2(3, EMPTY); public Object decide(object value) { a.assign(i, i, i+1, i); int other = a.read((i+2) % 3); if (other==EMPTY|| other==a.read(1)) return proposed[i]; else return proposed[j]; }} (4) (4) Other thread moved later so I win
109. Multi-Consensus Code class MultiConsensus extends …{ Assign2 a = new Assign2(3, EMPTY); public Object decide(object value) { a.assign(i, i, i+1, i); int other = a.read((i+2) % 3); if (other==EMPTY||other==a.read(1)) return proposed[i]; else return proposed[j]; }} (4) (4) OK, I win.
110. Multi-Consensus Code class MultiConsensus extends …{ Assign2 a = new Assign2(3, EMPTY); public Object decide(object value) { a.assign(i, i, i+1, i); int other = a.read((i+2) % 3); if (other==EMPTY||other==a.read(1)) return proposed[i]; else return proposed[j]; }} (4) (4) Other thread moved first, so I lose (1)
111.
112.
113. Read-Modify-Write public abstract class RMWRegister { private int value; public int synchronized getAndMumble() { int prior = this. value; this. value = mumble( this. value); return prior; } } (1)
114. Read-Modify-Write public abstract class RMWRegister { private int value; public int synchronized getAndMumble() { int prior = this. value; this.value = mumble(this.value); return prior; } } (1) Return prior value
115. Read-Modify-Write public abstract class RMWRegister { private int value; public int synchronized getAndMumble() { int prior = this.value; this. value = mumble( this. value); return prior; } } (1) Apply function to current value
116.
117. Example: Read public abstract class RMWRegister { private int value; public int synchronized read() { int prior = this. value; this. value = this. value; return prior; } } (1)
118. Example: Read public abstract class RMW { private int value; public int synchronized read() { int prior = this.value; this. value = this. value; return prior; } } (1) Apply f(v)=v , the identity function
119. Example: getAndSet public abstract class RMWRegister { private int value; public int synchronized getAndSet( int v) { int prior = this. value; this. value = v; return prior; } … } (1)
120. Example: getAndSet (swap) public abstract class RMWRegister { private int value; public int synchronized getAndSet(int v) { int prior = this.value; this. value = v; return prior; } … } (1) F(x)=v is constant function
121. getAndIncrement public abstract class RMWRegister { private int value; public int synchronized getAndIncrement() { int prior = this. value; this. value = this .value + 1; return prior; } … } (1)
122. getAndIncrement public abstract class RMWRegister { private int value; public int synchronized getAndIncrement() { int prior = this.value; this. value = this .value + 1; return prior; } … } (1) F(x) = x+1
123. getAndAdd public abstract class RMWRegister { private int value; public int synchronized getAndAdd( int a) { int prior = this. value; this. value = this .value + a; return prior; } … } (1)
124. Example: getAndAdd public abstract class RMWRegister { private int value; public int synchronized getAndIncrement(int a) { int prior = this.value; this. value = this .value + a; return prior; } … } (1) F(x) = x+a
125. compareAndSet public abstract class RMWRegister { private int value; public boolean synchronized compareAndSet( int expected, int update) { int prior = this. value; if ( this .value==expected) { this .value = update; return true ; } return false ; } … } (1)
126. compareAndSet public abstract class RMWRegister { private int value; public boolean synchronized compareAndSet(int expected, int update) { int prior = this.value; if ( this .value==expected ) { this.value = update; return true; } return false; } … } (1) If value is what was expected, …
127. compareAndSet public abstract class RMWRegister { private int value; public boolean synchronized compareAndSet(int expected, int update ) { int prior = this.value; if (this.value==expected) { this .value = update; return true; } return false; } … } (1) … replace it
128. compareAndSet public abstract class RMWRegister { private int value; public boolean synchronized compareAndSet(int expected, int update) { int prior = this.value; if (this.value==expected) { this.value = update; return true ; } return false; } … } (1) Report success
129. compareAndSet public abstract class RMWRegister { private int value; public boolean synchronized compareAndSet(int expected, int update) { int prior = this.value; if (this.value==expected) { this.value = update; return true; } return false ; } … } (1) Otherwise report failure
130. Read-Modify-Write public abstract class RMWRegister { private int value; public void synchronized getAndMumble() { int prior = this.value; this.value = mumble( this. value); return prior; } } (1) Lets characterize F(x)…
131.
132.
133.
134.
135. Proof public class RMWConsensus extends ConsensusProtocol { private RMWRegister r = v; public Object decide(object value) { propose(value); if (r.getAndMumble() == v) return proposed[i]; else return proposed[j]; }} (4)
136. Proof public class RMWConsensus extends ConsensusProtocol { private RMWRegister r = v; public Object decide(object value) { propose(value); if (r.getAndMumble() == v) return proposed[i]; else return proposed[j]; }} (4) Initialized to v
137. Proof public class RMWConsensus extends Consensus { private RMWRegister r = v; public Object decide(object value) { if (r.getAndMumble() == v) return proposed[i]; else return proposed[j]; }} (4) Am I first?
138. Proof public class RMWConsensus extends ConsensusProtocol { private RMWRegister r = v; public Object decide(object value) { propose(value); if (r.getAndMumble() == v) return proposed[i]; else return proposed[j]; }} (4) Yes, return my input
139. Proof public class RMWConsensus extends ConsensusProtocol { private RMWRegister r = v; public Object decide(object value) { propose(value); if (r.getAndMumble() == v) return proposed[i]; else return proposed[j]; }} (4) No, return other’s input
140.
141.
142.
143. Meanwhile Back at the Critical State c 0-valent 1-valent A about to apply f A B about to apply f B
144. Maybe the Functions Commute c 0-valent A applies f A B applies f B A applies f A B applies f B 0 1 C runs solo C runs solo 1-valent
145. Maybe the Functions Commute c 0-valent A applies f A B applies f B A applies f A B applies f B 0 1 C runs solo C runs solo 1-valent These states look the same to C Contradiction
146. Maybe the Functions Overwrite c 0-valent A applies f A B applies f B A applies f A 0 1 C runs solo C runs solo 1-valent
147. Maybe the Functions Overwrite c 0-valent A applies f A B applies f B A applies f A 0 1 C runs solo C runs solo 1-valent These states look the same to C Contradiction
148.
149. compareAndSet public abstract class RMWRegister { private int value; public boolean synchronized compareAndSet( int expected, int update) { int prior = this. value; if ( this .value==expected) { this .value = update; return true ; } return false ; } … } (1)
150. compareAndSet public abstract class RMWRegister { private int value; public boolean synchronized compareAndSet(int expected, int update) { int prior = this.value; if ( this .value==expected ) { this.value = update; return true; } return false; } … } (1) replace value if its what we expected, …
151. compareAndSet Has ∞ Consensus Number public class RMWConsensus extends ConsensusProtocol { private AtomicInteger r = new AtomicInteger(-1); public Object decide(object value) { propose(value); r.compareAndSet(-1,i); return proposed[r.get()]; } } (4)
152. compareAndSet Has ∞ Consensus Number public class RMWConsensus extends ConsensusProtocol { private AtomicInteger r = new AtomicInteger(-1); public Object decide(object value) { propose(value) r.compareAndSet(-1,i); return proposed[r.get()]; } } (4) Initialized to -1
153. compareAndSet Has ∞ Consensus Number public class RMWConsensus extends ConsensusProtocol { private AtomicInteger r = new AtomicInteger(-1); public Object decide(object value) { propose(value); r.compareAndSet(-1,i); return proposed[r.get()]; } } (4) Try to swap in my id
154. compareAndSet Has ∞ Consensus Number public class RMWConsensus extends ConsensusProtocol { private AtomicInteger r = new AtomicInteger(-1); public Object decide(object value) { propose(value); r.compareAndSet(-1,i); return proposed[r.get()]; } } (4) Decide winner’s preference
We looked at shared memory computation and tried to understand what we could build using the weakest assumptions regarding our shared medium.
We looked at wait-free implementations because they imply no mutual exclusion.
We started with the weakest primitive we could think of.
We built a collection of different synchronization objects all the way to an atomic snapshot. But what about constructing other objects like Queues. We know two thread queues can be implemented in a wait-free manner. What about queues with more than one enqueuer and dequeuer?
Wait-free implementations imply that operatiosn of different threads interleave at the granularity of individual machine instructions accessing memory.
As we will see later consensus can be formulated as a linearizable object in which threads in the sequential execution decide on the value of the first thread to complete.
This is one of the most fundamental theorems of distributed computing, proved originally by Fischer, Lynch, and Paterson in the 1980’s. It says that there are problems that are easily solvable by reading and writing to memory on a single processor, a turing machine, but are not solvable on a multiprocessor. As we will see, this will have profound implications on multiprocessor hardware design. We will give here a proof of their theorem in the context of shared memory as proven by Herlihy.
It suffices to prove the claim for two threads. Here is how we model computation histories.
Imagine that all the executions still decide on one input or another…assume the claim is false and lets show the set of possible executions must then include two executions with different values.
Since e
A critical state is a bivalent state in which the next step by one thread will lead to a 0-valent state and the next step by the other will lead to a 1-valent state
Why must there be such a critical state? Start from a bivalent state. If it is not critical, run A until about to make state 0-valent, if not a critical state, that is, if B’s step will not take the state to a 1-valent state, then run B until in is about to take system to a 1-valent state. If this state is not critical, run A again, then B, and so on. Since this is a wai-free execution, within a finite number of steps each will finish, so somewhere before they must reach a critical state.
Tp prove that we cannot solve consensus using read-write registers, lets go over all the possible interactions among the threads and show that for each such execution they lead to a contradiction.
This is the case in which some thread reads (and the other either reads or writes). Two possible executions, one in which A runs solo and decides 0 and another in which A runs after B takes its critical read step. Leads to a contradiction since A cannot tell that B has read…so two executions lead to the same state but the system is supposed to have two different valencies for it, a contradiction.
Consensus can be formulated as a linearizable object in which threads in the sequential execution decide on the value of the first thread to complete.
A linearizable object in which threads in the sequential execution decide on the value of the first thread to complete. The consensus objects we will consider will be one time objects, that is, each thread executes a method once.
Note the use of a protected for proposed. A protected method is visible to inheriting classes, even not part of the same package. A package scope (default) method is not. That is the only difference between protected and package scope. method are restricted in accessing it. See example below: package one; public class A { protected int p; } //////////////////////////////// package two; import one. A; class B extends A { void myMethod() { p = 1; // ok A a = new A(); a.p = 1; // not ok, p would have to be public for this to work. } }
The exceptions have to do with special non-deterministic objects or with how threads “bind” to the object or with the objects being “oblivious”. See chapter for details. The following is a summary from a paper on the subject by Hadzilacos et. al: Hadzilacos et al prove that if types can be nondeterministic and nonoblivious, and each process is required to bind permanently to at most one port of an object, then hrm is not robust. Concurrently with the rst appearance of our result [CHJT94], there appeared two papers proving the robustness of hrm in certain models [BGA94, PBN94]. Specically, Borowsky, Gafni, and Afek show that if types must be deterministic and oblivious, each process may bind to several ports of the same object, and the process-to-port binding is transient, then hrm is robust [BGA94]. Peterson, Bazzi, and Neiger show that if types must be deterministic, each process may bind to several ports of the same object, but the process-to-port binding is permanent, then hrm is robust [PBN94]. More nonrobustness results have appeared recently. Moran and Rappoport [MR96] strengthen our result by proving that hrm is not robust even if types must be deterministic, as long as each process must be permanently bound to at most one port of an object. Lo and Hadzilacos [LH97] prove that hrm is not robust if types can be nondeterministic, regardless of whether they are oblivious and of how processes may bind to ports. Schenk proves a result similar to the one in [LH97], but his result requires innite nondeterminism and applies only for a stronger denition of wait-free implementation [Sch96].
To save space and make the code clearer we wrote that it extends consensus while it actually extends ConsensusProtocol and omitted that Decide sets j=i-1 and then proposes value. Assume this appears in all the code.
NOTE: Lock-free is the same as wait-free if the execution is finite. “ Lock-free is to wait-free as deadlock-free is to lockout-free.” In other words, Lock-free and Deadlock-free are “stalinistic”, preferring group progress, while Wait-free and Lockout-free guarantee individual progress. Any wait-free implementation is lock-free.
NOTE: Lock-free is the same as wait-free if the execution is finite. “ Lock-free is to wait-free as deadlock-free is to lockout-free.” In other words, Lock-free and Deadlock-free are “stalinistic”, preferring group progress, while Wait-free and Lockout-free guarantee individual progress. Any wait-free implementation is lock-free.
NOTE: Lock-free is the same as wait-free if the execution is finite. “ Lock-free is to wait-free as deadlock-free is to lockout-free.” In other words, Lock-free and Deadlock-free are “stalinistic”, preferring group progress, while Wait-free and Lockout-free guarantee individual progress. Any wait-free implementation is lock-free.
NOTE: Lock-free is the same as wait-free if the execution is finite. “ Lock-free is to wait-free as deadlock-free is to lockout-free.” In other words, Lock-free and Deadlock-free are “stalinistic”, preferring group progress, while Wait-free and Lockout-free guarantee individual progress. Any wait-free implementation is lock-free.
Consensus can be formulated as a linearizable object in which threads in the sequential execution decide on the value of the first thread to complete.