obstruction-free synchronization

30
Obstruction-free synchronization Article by: Maurice Herlihy, Victor Luchangco, Mark Moir Double-Ended Queues as an example Presentation : Or Peri

Upload: abeni

Post on 22-Feb-2016

61 views

Category:

Documents


0 download

DESCRIPTION

Obstruction-free synchronization. Double-Ended Queues as an example. Article by: Maurice Herlihy , Victor Luchangco , Mark Moir. Presentation : Or Peri. Today’s Agenda. Two obstruction-free, CAS-based implementations of Double-ended queues . Linear array - PowerPoint PPT Presentation

TRANSCRIPT

Obstruction-free synchronization

Article by: Maurice Herlihy, Victor Luchangco,

Mark Moir

Double-Ended Queues as an example

Presentation : Or Peri

Today’s Agenda• Two obstruction-free, CAS-based implementations

of Double-ended queues.o Linear arrayo Circular array

Why Obstruction-Free?

• Avoid locks.• Non-blocking data sharing between threads.• Greater flexibility in design compared with Lock-

freedom and wait-freedom implementations.• In practice, should provide the benefits of wait-

free and lock-free programming.

What’s wrong with Locks?

• Deadlocks• Low liveness• Fault-handling• Scalability

• Obstruction-freedom ensures No thread can be blocked by delays or failures of other threads.

• Obstruction-free algorithms are simpler, and can be applied to complex structures.

• It does not guarantee progress when two (or more) conflicting threads execute concurrently.

• To improve progress one might add a contention reducing mechanism (“back-off reflex”).

Pros & cons

• lock-free and wait-free implementations use such mechanisms, but in a way that imposes a large overhead, even without contention.

• In scenarios with low contention, programming an Obstruction-free algorithm with some contention-manager, there’s the benefit from the simple and efficient design.

Pros & cons

• Double-ended queue- generalize FIFO queues and LIFO stacks.

DEqueues

• Allows push\pop operations in both ends.

• Remember “Job Stealing”?• One application of DEqueues is as processors’ jobs queues.

• Each processor pops tasks from it’s own Dequeue’s head.

DEqueues- what for?

JobJob

Job

JobJobJob

Job

• Upon fork(), it pushes tasks to it’s DEqueue‘s head.

• If a processor’s queue is empty, it can “steal” tasks from another processor’s DEqueue‘s tail.

DEqueues- what for?

Job

JobJobJob

Job

Job

JobJob

• First we’ll see the simpler, linear, array-based DEqueue.

• Second stage will extend the first one to “wrap around” itself.

Implementation

• Two special “null” values: LN and RN

• Array A[0,…,MAX+1] holds state.• MAX is the queue’s maximal capacity.• INVARIANT: the state will hold: LN+ values* RN+

• An Oracle() function:o Parameter: left/righto Returns: an array index

• When Oracle(right) is invoked, the returned index is the leftmost RN value in A.

Implementation – Intro

• Each element i in A has:o A value: i.valo A version counter: i.ctr

• Version numbers are updated at every CAS operation.

• Linearization point: point of changing a value in A.

Implementation – Intro

• The Idea: o rightpush(v) will change the leftmost RN to v.o rightpop() will change the rightmost data to RN (and

return it)

o rightpush(v) returns “full” if there’s a non-RN value at A[MAX]

o rightpop() returns “empty” if there are neighboring RN,LN

• Right/left push/pop are symmetric, so we only show one side.

Implementation – Intro

1) Rightpush(v){2) While(true){3) k := oracle(right);4) prev := A[k-1];5) cur := A[k];6) if(prev.val != RN and cur.val = RN){7) if(k = MAX+1) return “full”;8) if( CAS(&A[k-1], prev,

<prev.val,prev.ctr+1>) )9) if( CAS(&A[k], cur, <v,cur.ctr+1>) )10) return “ok”;11) } //end “if”12) } //end “while”13) } //end func

Implementation – right push

1) Rightpop(){2) While(true){3) k := oracle(right);4) cur := A[k-1];5) next := A[k];6) if(cur.val != RN and next.val = RN){7) if(cur.val = LN and A[k-1] = cur) 8) return “empty”;9) if( CAS(&A[k], next, <RN, next.ctr+1>) )10) if( CAS(&A[k-1], cur, <RN,cur.ctr+1>) )11) return cur.val;12) } //end “if”13) } //end “while”14) } //end func

Implementation – right pop

• Relies on three claims: o In a rightpush(v) operation, at the moment we “CAS“

A[k].val from an RN value to v, A[k-1].val is not RN. o In a rightpop() operation, at the moment we “CAS” A[k-

1].val from some v to RN, A[k].val contains RN.o If rightpop() returns “empty”, then at the moment it

performed next:=A[k] (and just after: cur:=A[k-1]), these two values were LN and RN.

Linearizability

• The third claim:o If rightpop() returns “empty”, then at the moment it

performed next:=A[k] (and just after: cur:=A[k-1]), these two values were LN and RN.

• holds since: 4) cur := A[k-1];5) next := A[k];6) if(cur.val != RN and next.val = RN){7) if(cur.val = LN and A[k-1] = cur) 8) return “empty”;

• A[k-1] didn’t change version number from line 4 to 7• so did A[k] from line 5 to 6.

Linearizability

• The first two claims hold similarly:o Since CAS operations check version numbers, only if no one

interfered with another push/pop, we can perform the operationo In rightpush(v) for example:

4) prev := A[k-1];5) cur := A[k];6) if(prev.val != RN and cur.val = RN){7) if(k = MAX+1) return “full”;8) if( CAS(&A[k-1], prev, <prev.val,prev.ctr+1>) )9) if( CAS(&A[k], cur, <v,cur.ctr+1>) )

• Counter didn’t change (upon success) from line 5 to 9, hence so did the value.

• Same holds for the neighbor (k-1) from line 4 to 8

Linearizability

• Implementing the Oracle() function:o For linearizability, we only need oracle() to return an index at range.o For Obstruction-freedom we have to show that it is eventually

accurate if invoked repeatedly without interference.

• Naïve approach is to simply go over the entire array and look for the first RN.

• Another approach is to keep “hints” (last position, for instance), and search around them.

• We can update these hints frequently or seldom with respect to cache locations… but that’s off-topic

Linearizability

• The Idea: o A[0] is “immediately to the right” of A[MAX+1].o All indices are calculated modulo MAX+2.

• Two main differences:o To return “full” we must be sure there are exactly two null entries.o A rightpush operation may encounter a LN value we’ll convert them

into RN values (using another null character: DN).

Extension to circular array

• All null values are in a contiguous sequence in the array.

• This sequence is of the form: RN* DN* LN*• There are at least 2 different types of null values

in the sequence.

Circular array - Invariants

• We don’t invoke oracle(right) directly. • Instead, we have rightCheckOracle() which

returns: o K an array indexo Left A[k-1]’s last contento Right A[k]’s last content

• This guarantees: o right.val = RNo Left.val != RN

Circular array - Implementation

rightCheckedOracle()1) While(true){2) k := oracle(right);3) left := A[k-1];4) right := A[k];5) if(right.val = RN and left.val != RN)6) return k,left,right;7) if( right.val = DN and !(left.val in {RN,DN}) )8) if( CAS(&A[k-1], left, <left.val,

left.ctr+1>) )9) if( CAS(&A[k], right,

<RN,cur.ctr+1>) )10) return k,<left.val,left.ctr+1>, <RN,right.ctr+1>;11) } //end “while”

• The array is not “full” when A[k+1] is RN.• this is since A[k] is RN and an Invariant holds

that “There are at least 2 different types of null

values in the sequence”.

• So, if A[k+1] = LN try converting it to DN• If A[k+1] = DN try converting it to RN• In this case, we need to check “nextnext”.

The major change – rightPush(v)

rightPush(v)1) While(true){2) k,prev,cur := rightCheckedOracle();3) next := A[k+1];4) if( next.val = RN ) //change RN to v5) if( CAS(&A[k-1], prev,

<prev.val,prev.ctr+1> ) )6) if( CAS(&A[k], cur, <v,cur.ctr+1>) )7) return “ok”;8) if( next.val = LN ) //change LN to DN9) if( CAS(&A[k], cur, <RN, cur.ctr+1>) )10) if( CAS(&A[k+1], next,

<DN,next.ctr+1>) )11) if(next.val = DN)

rightPush(v)11) if(next.val = DN){12) nextnext:= A[k+2];13) if( !(nextnext.val in {RN,LN,DN}) )14) if(A[k-1] = prev)15) if(A[k] = cur)16) return “full”;17) if( nextnext.val = LN) //DN to RN18) if( CAS(&A[k+2], nextnext, <nextnext.val,nextnext.ctr+1>) )19) CAS(&A[k+1], next,

<RN,next.ctr+1>);20) } //end “if”21)}//end “while”

rightPop()1) While(true){2) k,cur,next := rightCheckedOracle();3) if( cur.val in {LN,DN} and A[k-1] = cur )4) return “empty”;5) if( CAS(&A[k], next, <RN, next.ctr+1>) )6) if( CAS(&A[k-1], cur,

<RN,cur.ctr+1>) )7) return cur.val;8) }//end “while”

• Is harder to prove in this case (there’s a whole other article just to do so).

• The main difficulty: proving that when rightPush(v) changes a value, it has an RN or an DN to it’s right.

• There are 5 lines in the code (of the right side functions) which may interrupt with this, but they are all using CAS, and intuitively, the .ctr values should assure correctness.

Linearizability

• We’ve seen Two Obstruction-free implementations of a Dequeue.

• As promised, they are pretty simple.• Hopefully, I’ve managed to demonstrate the main

degradation, as well as an intuition as to why it’s a good solution for relatively low contention scenarios

To Sum up

Questions?

?