22.11.2014 Views

Formal Verification of Lock-Free Algorithms

Formal Verification of Lock-Free Algorithms

Formal Verification of Lock-Free Algorithms

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

Top<br />

a b c<br />

null<br />

Figure 1. Example <strong>of</strong> a Stack representation<br />

CAS(Old,New,G : Pointer){<br />

if G = Old<br />

then {G := New; return TRUE; }<br />

else return FALSE;<br />

}<br />

Figure 2. The atomic CAS operation<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

10<br />

CPOP(Top : Pointer ) {<br />

repeat<br />

OTop := Top ;<br />

if OTop = null then<br />

return empty;<br />

ONxt := OTop .nxt ;<br />

Res := OTop .val ;<br />

until CAS(OTop , ONxt , Top );<br />

return Res<br />

}<br />

Figure 4. The pop operation<br />

field for the data value and a .nxt pointer to refer to the<br />

succeeding node. The end <strong>of</strong> the stack is marked with the<br />

null pointer. The entry point <strong>of</strong> the stack, i.e. the top cell,<br />

is referenced by the global variable Top.<br />

The push and pop operations consist <strong>of</strong> two phases. First,<br />

the needed data is prepared without changing the main<br />

data structure, e.g. storage is allocated or data are read. In<br />

a second phase, it is attempted to change the stack data<br />

structure in a single atomic step. If this atomic step fails<br />

(e.g. because the data structure has changed), the main data<br />

structure is not changed and the algorithm repeats phase<br />

one. To change the stack atomically, a compare-and-swap<br />

(CAS) command is used. Pseudocode for this operation is<br />

given in Figure 2. The CAS command compares a local<br />

pointer Old with a global pointer G, where Old is typically<br />

a value <strong>of</strong> G, that was read earlier by the process. If both<br />

pointers are identical, G is set to a new value New and<br />

the CAS command succeeds. If the values are different, G<br />

is left unchanged and the CAS command fails. All usual<br />

multi-core processors support atomic CAS instructions (e.g.<br />

CMPXCHG on x86 processors) or equivalent instructions.<br />

The push algorithm is depicted in Figure 3. Parameters <strong>of</strong><br />

CPUSH are the value input value V, which should be placed<br />

on top <strong>of</strong> the stack, and a reference to the Top pointer.<br />

Since each invocation <strong>of</strong> CPUSH runs in parallel with calls<br />

to CPUSH or CPOP from other processes, Top may change<br />

at any time while the algorithm is executed.<br />

Variables UNew and UTop are local pointer variables.<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

CPUSH(V : Data, Top : Pointer){<br />

UNew := new(Node);<br />

UNew .val := V;<br />

repeat {<br />

UTop := Top ;<br />

UNew .nxt := UTop ;<br />

} until CAS(UTop , UNew , Top )<br />

}<br />

Figure 3. The push operation<br />

The algorithm starts by allocating a new cell on the heap<br />

and writing the input value V into the newly allocated node<br />

(line 2 and 3). After that the algorithm loops, as long as the<br />

insertion <strong>of</strong> the new cell in the stack fails (line 4 to 7). Inside<br />

the loop the pointer <strong>of</strong> the current top cell is copied to the<br />

local variable UTop and the .nxt-pointer <strong>of</strong> the previously<br />

allocated cell is set to the current top cell (line 5 and 6).<br />

Finally, the algorithms attempts to add the new data value<br />

to the top <strong>of</strong> the stack data structure by a CAS operation<br />

(line 7). If the current Top is still the same as it was in line<br />

5, the previously allocated cell UNew contains the correct<br />

.nxt -pointer and Top can be set to UNew . If the top <strong>of</strong><br />

stack was changed by another push or pop process in the<br />

meantime, the CAS operation fails and the loop is reiterated.<br />

The pop algorithm, shown in Figure 4, is very similar.<br />

The only difference is that it has to be checked, whether the<br />

stack is empty or not (line 4). In case <strong>of</strong> an empty stack, a<br />

special value empty is returned.<br />

3. Linearizability and <strong>Lock</strong>-<strong>Free</strong>dom<br />

The usual correctness criteria for lock-free algorithms are<br />

linearizability for safety and lock-freedom for liveness.<br />

Linearizability was defined by Herlihy & Wing [5]. It<br />

is based on the visible inputs and outputs <strong>of</strong> processes.<br />

<strong>Formal</strong>ly a history is defined as a list <strong>of</strong> invoke and return<br />

events. An invoke event inv(p,op,in) is added to the history,<br />

when process p invokes operation op with input in. Similarly<br />

a return event ret(p,op,out) is added when process p returns<br />

with output out from operation op. In our example a possible<br />

history created by two processes p and q is<br />

H = [inv(p,push,3), inv(q,push,4), ret(p,push,−),<br />

ret(q,push,−), inv(q,pop,−), ret(q,pop,4)]<br />

where the − indicates no input/output. A history is linearizable<br />

if it can be reordered to a sequential history, where each<br />

invoke is directly followed by the corresponding return. The<br />

reordering must respect the order that was present in the<br />

original history: an operation whose invoke happened after<br />

the return <strong>of</strong> another operation, must end up in the same<br />

14<br />

Authorized licensed use limited to: Technische Universitat Kaiserslautern. Downloaded on January 18, 2010 at 11:43 from IEEE Xplore. Restrictions apply.

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!