Loading in 5 sec....

Obstruction-free synchronizationPowerPoint Presentation

Obstruction-free synchronization

- By
**abeni** - Follow User

- 114 Views
- Uploaded on

Download Presentation
## PowerPoint Slideshow about ' Obstruction-free synchronization' - abeni

**An Image/Link below is provided (as is) to download presentation**

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.

- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript

### 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
- 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

Pros & cons

- Obstruction-freedom ensures No thread can be blockedby 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.

DEqueues

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

- Allows push\pop operations in both ends.

DEqueues- what for?

- Remember “Job Stealing”?
- One application of DEqueues is as processors’ jobs queues.
- Each processor pops tasks from it’s own Dequeue’shead.

Job

Job

Job

Job

Job

Job

Job

DEqueues- what for?

- Upon fork(), it pushes tasks to it’s DEqueue‘shead.
- If a processor’s queue is empty, it can “steal” tasks from another processor’s DEqueue‘stail.

Job

Job

Job

Job

Job

Job

Job

Job

Implementation

- First we’ll see the simpler, linear, array-based DEqueue.
- Second stage will extend the first one to “wrap around” itself.

Implementation – Intro

- Two special “null” values: LNand 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:
- Parameter: left/right
- 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:
- A value: i.val
- 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:
- rightpush(v) will change the leftmost RN to v.
- rightpop() will change the rightmost data to RN (and return it)
- rightpush(v) returns “full” if there’s a non-RN value at A[MAX]
- rightpop() returns “empty” if there are neighboring RN,LN

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

Implementation – right push

- Rightpush(v){
- While(true){
- k := oracle(right);
- prev := A[k-1];
- cur := A[k];
- if(prev.val != RN and cur.val = RN){
- if(k = MAX+1) return “full”;
- if( CAS(&A[k-1], prev, <prev.val,prev.ctr+1>) )
- if( CAS(&A[k], cur, <v,cur.ctr+1>) )
- return “ok”;
- } //end “if”
- } //end “while”
- } //end func

Implementation – right pop

- Rightpop(){
- While(true){
- k := oracle(right);
- cur := A[k-1];
- next := A[k];
- if(cur.val != RN and next.val = RN){
- if(cur.val = LN and A[k-1] = cur)
- return “empty”;
- if( CAS(&A[k], next, <RN, next.ctr+1>) )
- if( CAS(&A[k-1], cur, <RN,cur.ctr+1>) )
- returncur.val;
- } //end “if”
- } //end “while”
- } //end func

Linearizability

- Relies on three claims:
- 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.
- In a rightpop() operation, at the moment we “CAS” A[k-1].val from some v to RN, A[k].val contains RN.
- 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:
- 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:
- cur := A[k-1];
- next := A[k];
- if(cur.val!= RN and next.val = RN){
- if(cur.val= LN and A[k-1] = cur)
- 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:
- Since CAS operations check version numbers, only if no one interfered with another push/pop, we can perform the operation
- In rightpush(v) for example:

- prev := A[k-1];
- cur := A[k];
- if(prev.val!= RN and cur.val = RN){
- if(k = MAX+1) return “full”;
- if( CAS(&A[k-1], prev, <prev.val,prev.ctr+1>) )
- 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:
- For linearizability, we only need oracle() to return an index at range.
- 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

Extension to circular array

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

- Two main differences:
- To return “full” we must be sure there are exactly two null entries.
- A rightpushoperation may encounter a LN value we’ll convert them into RN values (using another null character: DN).

Circular array - Invariants

- 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 - Implementation

- We don’t invoke oracle(right) directly.
- Instead, we have rightCheckOracle() which returns:
- K an array index
- Left A[k-1]’s last content
- Right A[k]’s last content

- This guarantees:
- right.val = RN
- Left.val != RN

rightCheckedOracle()

- While(true){
- k := oracle(right);
- left := A[k-1];
- right := A[k];
- if(right.val = RN and left.val != RN)
- returnk,left,right;
- if( right.val = DN and !(left.val in {RN,DN}) )
- if( CAS(&A[k-1], left, <left.val, left.ctr+1>) )
- if( CAS(&A[k], right, <RN,cur.ctr+1>) )
- return k,<left.val,left.ctr+1>,
<RN,right.ctr+1>;

- } //end “while”

The major change – rightPush(v)

- 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”.

rightPush(v)

- While(true){
- k,prev,cur := rightCheckedOracle();
- next := A[k+1];
- if( next.val = RN ) //change RN to v
- if( CAS(&A[k-1], prev, <prev.val,prev.ctr+1> ) )
- if( CAS(&A[k], cur, <v,cur.ctr+1>) )
- return “ok”;
- if( next.val = LN ) //change LN to DN
- if( CAS(&A[k], cur, <RN, cur.ctr+1>) )
- if( CAS(&A[k+1], next, <DN,next.ctr+1>) )
- if(next.val = DN)

rightPush(v)

- if(next.val = DN){
- nextnext:= A[k+2];
- if( !(nextnext.val in {RN,LN,DN}) )
- if(A[k-1] = prev)
- if(A[k] = cur)
- return “full”;
- if( nextnext.val = LN) //DN to RN
- if( CAS(&A[k+2], nextnext,
<nextnext.val,nextnext.ctr+1>) )

- CAS(&A[k+1], next, <RN,next.ctr+1>);
- } //end “if”
- }//end “while”

rightPop()

- While(true){
- k,cur,next := rightCheckedOracle();
- if( cur.val in {LN,DN} and A[k-1] = cur )
- return “empty”;
- if( CAS(&A[k], next, <RN, next.ctr+1>) )
- if( CAS(&A[k-1], cur, <RN,cur.ctr+1>) )
- returncur.val;
- }//end “while”

Linearizability

- 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.

To Sum up

- 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

Download Presentation

Connecting to Server..