Computing with anonymous processes Prof R. Guerraoui Distributed Programming Laboratory © R. Guerraoui
Counter (sequential spec) A counter has two operations inc() and read() and maintains an integer x init to 0 read(): return(x) inc(): x := x + 1; return(ok) We focus on a simple counter that can be implemented with a register One with different operations: read and inc()
Counter (atomic implementation) The processes share an array of SWMR registers Reg1,..,n ; the writer of register Regi is pi inc(): temp := Regi.read() + 1; Regi.write(temp); return(ok)
Counter (atomic implementation) read(): sum := 0; for j = 1 to n do sum := sum + Regj.read(); return(sum)
Weak Counter A weak counter has one operation wInc() wInc(): x := x + 1; return(x) Correctness: if an operation precedes another, then the second returns a value that is larger than the first one An operation should not return a value that is higher than the number of operations that occured Two concurrent operations can return the same value
Weak counter execution wInc() - 1 p1 wInc() - 2 p2 wInc() - 2 p3
(lock-free implementation) Weak Counter (lock-free implementation) The processes share an (infinite) array of MWMR registers Reg1,..,n,..,, init to 0 wInc(): i := 0; while (Regi.read() ≠ 0) do i := i + 1; Regi.write(1); return(i); Every process looks for a free place to store a value; if an operation wInc1 precedes wInc2, then wInc2 returns a higher value
Weak counter execution wInc() - 1 wInc() - 2 wInc() - p1 p2 wInc() - p3
(wait-free implementation) Weak Counter (wait-free implementation) The processes also use a MWMR register L wInc(): i : = 0; while (Regi.read() ≠ 0) do if L has been updated n times then return the largest value seen in L i := i + 1; L.write(i); Regi.write(1); return(i); The trick here is for the fastest process to help the slow ones; if a fast process updates L while a slow one is trying to wInc, then the slow can return that value; the slow one needs to make sure that the fast one did not finish earlier… so we need to wait for n updates (at least one updated twice since wInc of the slow started) It is important to return the largest value in L otherwise a process might write a smaller value; In fact, we ensure that if pi returns a value from pj, then pj started after pi started
(wait-free implementation) Weak Counter (wait-free implementation) wInc(): t := l := L.read(); i := k:= 0; while (Regi.read() ≠ 0) do i : = i + 1; if L.read() ≠ l then l := L.read(); t := max(t,l); k :=k+1; if k = n then return(t); L.write(i); Regi.write(1); return(i);
Snapshot (sequential spec) A snapshot has operations update() and scan() and maintains an array x of size n scan(): return(x) NB. No component is devoted to a process update(i,v): xi := v; return(ok)
Key idea for atomicity & wait-freedom The processes share a Weak Counter: Wcounter, init to 0; The processes share an array of registers Reg1,..,N that contains each: a value, a timestamp, and a copy of the entire array of values The main modification is the way we get the counter value; Updating is trivial: we get a timestamp and we write To scan, we need to read until we find the same values; now we need to make sure that enough reads are the same; in a non anonymous system, it is ok to see n same reads;
& wait-freedom (cont’d) Key idea for atomicity & wait-freedom (cont’d) To scan, a process keeps collecting and returns a collect if it did not change, or some collect returned by a concurrent scan Timestamps are used to check if a scan has been taken in the meantime To update, a process scans and writes the value, the new timestamp and the result of the scan A process needs to see q = m(n-1) + 2 similar values to return a scan
Snapshot implementation Every process keeps a local timestamp ts update(i,v): ts := Wcounter.wInc(); Regi.write(v,ts,self.scan()); return(ok)
Snapshot implementation scan(): ts := Wcounter.wInc(); while(true) do If some Regj contains a collect with a higher timestamp than ts, then return that collect If n+1 sets of reads return identical results then return that one In the traditional lock-free scan, if two are the same, then we can return them
Consensus (obstruction-free) We consider binary consensus The processes share two infinite arrays of registers: Reg0i and Reg1i Every process holds an index integer i, init to 1 Idea: to impose a value v, a process needs to be fast enough to fill in registers in Regvi Value 0 #1 #2 #3 #4 … Value 1 #1 #2 #3 #4 …
If we’re leading by 2, we won! If we’re losing, I switch teams! Consensus (obstruction-free) My team may be winning propose(v): while(true) do if Reg1-vi = 0 then Regvi := 1; if i > 1 and Reg1-vi-1 = 0 then return(v); else v:= 1-v; i := i+1; end Score 1 for my team If we’re leading by 2, we won! Assume p is alone and proposes 1; p looks at Reg0(1); if it is empty, p moves to Reg1(1) and fills it; then p goes to Reg0(2), if it is empty, then p fills in Reg1(2); if Reg1(1) is still empty then p decides 0; so if p proposing 1 is running alone, it decides 1 if Reg0(1) and Reg0(2) are empty and p fills in Reg1(1) and Reg2(1); If q comes later with 1 (all fine); if q comes with 0; q finds Reg1(1) = 1 and Reg2(1) = 1; If we’re losing, I switch teams!
A simple execution Team 0 vs Team 1 Solo execution: Process p1 (green) comes in alone, and marks the first two slots of Reg1 Processes that come later either have value 1 and decide 1, or switch to value 1 and decide 1 Value 0 #1 #2 #3 #4 … Value 1 #1 #2 #3 #4 … 1
Lock-step execution Team 0 vs Team 1 Lock-step: If the two processes proceed in perfect lock-step, then the algorithm will go on forever Obstruction-free, but not wait-free Value 0 #1 #2 #3 #4 … Value 1 #1 #2 #3 #4 … 1
Algorithm tip When designing a concurrent algorithm, it helps to first check correctness in solo and lock-step executions
Can we make it wait-free? We need to assume eventual synchrony Definition: In every execution, there exists a time GST (global stabilization time) after which the processes’ internal clocks are perfectly synchronized
One of the teams becomes slower! Consensus (binary) propose(v): while(true) do If Reg1-vi = 0 then Regvi := 1; if i > 1 and Reg1-vi-1 = 0 then return(v); else if Regvi = 0 then v:= 1-v; if v = 1 then wait(2i) i := i+1; end One of the teams becomes slower!
Wait-free (intuition) Team 0 vs Team 1 Lock-step: The processes in team 1 have to wait for 2i steps after each loop Hence, eventually, they become so slow that team 0 wins Value 0 #1 #2 #3 #4 … Value 1 #1 #2 #3 #4 … 1
References Writeup containing all algorithms and more: http://ic2.epfl.ch/publications/documents/IC_TECH_REPORT_200496.pdf