lecture 6-2 : concurrent queues and stacks companion slides for the art of multiprocessor...

Post on 16-Dec-2015

218 Views

Category:

Documents

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Lecture 6-2 :Concurrent Queues and

Stacks

Companion slides forThe Art of Multiprocessor

Programmingby Maurice Herlihy & Nir Shavit

pool

• Data Structure similar to Set– Does not necessarily provide contains()

method– Allows the same item to appear more than

once– get() and set()

Art of Multiprocessor Programming© Herlihy-Shavit 2007

2

public interface Pool<T> {

void put(T item);

T get();

}

Art of Multiprocessor Programming© Herlihy-Shavit 2007

3

Queues & Stacks

• Both: pool of items• Queue

– enq() & deq()– First-in-first-out (FIFO) order

• Stack– push() & pop()– Last-in-first-out (LIFO) order

Art of Multiprocessor Programming© Herlihy-Shavit 2007

4

Bounded vs Unbounded

• Bounded– Fixed capacity– Good when resources an issue

• Unbounded– Holds any number of objects

Art of Multiprocessor Programming© Herlihy-Shavit 2007

5

Blocking vs Non-Blocking

• Problem cases:– Removing from empty pool– Adding to full (bounded) pool

• Blocking– Caller waits until state changes

• Non-Blocking– Method throws exception

Art of Multiprocessor Programming© Herlihy-Shavit 2007

6

Queue: Concurrency

enq(x) y=deq()

enq() and deq() work at

different ends of the object

tail head

Art of Multiprocessor Programming© Herlihy-Shavit 2007

7

Concurrency

enq(x)

Challenge: what if the queue is empty or full?

y=deq()

tailhead

Art of Multiprocessor Programming© Herlihy-Shavit 2007

8

A Bounded Lock-Based Queue

public class BoundedQueue<T> {

ReentrantLock enqLock, deqLock;

Condition notEmptyCondition, notFullCondition;

AtomicInteger size;

Node head;

Node tail;

int capacity;

public BoundedQueue(int capacity) {

this.capacity = capacity;

this.head = new Node(null);

this.tail = head;

this.size = new AtomicInteger(0);

this.enqLock = new ReentrantLock();

this.notFullCondition = enqLock.newCondition();

this.deqLock = new ReentrantLock();

this.notEmptyCondition = deqLock.newCondition();

}

protected class Node {

public T value;

public Node next;

public Node(T x) {

value = x;

next = null;

}

}

9

public void enq(T x) {

boolean mustWakeDequeuers = false;

enqLock.lock();

try {

while (size.get() == capacity)

notFullCondition.await();

Node e = new Node(x);

tail.next = e;

tail = e;

if (size.getAndIncrement() == 0) {

mustWakeDequeuers = true;

}

} finally {

enqLock.unlock();

}

if (mustWakeDequeuers) {

deqLock.lock();

try {

notEmptyCondition.signalAll();

} finally {

deqLock.unlock();

}

}

}

public T deq() {

T result;

boolean mustWakeEnqueuers = false;

deqLock.lock();

try {

while (size.get() == 0) {

notEmptyCondition.await();

result = head.next.value;

head = head.next;

if (size.getAndDecrement() == capacity) {

mustWakeEnqueuers = true;

}

} finally {

deqLock.unlock();

}

if (mustWakeEnqueuers) {

enqLock.lock();

try {

notFullCondition.signalAll();

} finally {

enqLock.unlock();

}

}

return result;

}

lock

• enqLock/deqLock– At most one enqueuer/dequeuer at a time can

manipulate the queue’s fields

• Two locks– Enqueuer does not lock out dequeuer – vice versa

• Association– enqLock associated with notFullCondition– deqLock associated with notEmptyCondition

Art of Multiprocessor Programming© Herlihy-Shavit 2007

10

enqueue1. Acquires enqLock2. Reads the size field3. If full, enqueuer must wait until dequeuer makes room4. enqueuer waits on notFullCondition field, releasing

enqLock temporarily, and blocking until that condition is signaled.

5. Each time the thread awakens, it checks whether there is a room, and if not, goes back to sleep

6. Insert new item into tail7. Release enqLock8. If queue was empty, notify/signal waiting dequeuers

Art of Multiprocessor Programming© Herlihy-Shavit 2007

11

dequeue1. Acquires deqLock2. Reads the size field3. If empty, dequeuer must wait until item is enqueued4. dequeuer waits on notEmptyCondition field, releasing

deqLock temporarily, and blocking until that condition is signaled.

5. Each time the thread awakens, it checks whether item was enqueued, and if not, goes back to sleep

6. Assigne the value of head’s next node to “result” and reset head to head’s next node

7. Release deqLock8. If queue was full, notify/signal waiting enqueuers9. Return “result”

Art of Multiprocessor Programming© Herlihy-Shavit 2007

12

Art of Multiprocessor Programming 1313

Bounded Queue

Sentinel

head

tail

Art of Multiprocessor Programming 1414

Bounded Queue

head

tail

First actual item

Art of Multiprocessor Programming 1515

Bounded Queue

head

tail

Lock out other deq()

calls

deqLock

Art of Multiprocessor Programming 1616

Bounded Queue

head

tail

Lock out other enq()

calls

deqLock

enqLock

Art of Multiprocessor Programming 1717

Not Done Yet

head

tail

deqLock

enqLock

Need to tell whether queue is full or

empty

Art of Multiprocessor Programming 1818

Not Done Yet

head

tail

deqLock

enqLock

Max size is 8 items

size

1

Art of Multiprocessor Programming 1919

Not Done Yet

head

tail

deqLock

enqLock

Incremented by enq()Decremented by deq()

size

1

Art of Multiprocessor Programming 2020

Enqueuer

head

tail

deqLock

enqLock

size

1

Lock enqLock

Art of Multiprocessor Programming 2121

Enqueuer

head

tail

deqLock

enqLock

size

1

Read size

OK

Art of Multiprocessor Programming 2222

Enqueuer

head

tail

deqLock

enqLock

size

1

No need to lock tail

Art of Multiprocessor Programming 2323

Enqueuer

head

tail

deqLock

enqLock

size

1

Enqueue Node

Art of Multiprocessor Programming 2424

Enqueuer

head

tail

deqLock

enqLock

size

12

getAndincrement()

Art of Multiprocessor Programming 2525

Enqueuer

head

tail

deqLock

enqLock

size

8 Release lock2

Art of Multiprocessor Programming 2626

Enqueuer

head

tail

deqLock

enqLock

size

2

If queue was empty, notify

waiting dequeuers

Art of Multiprocessor Programming 2727

Unsuccesful Enqueuer

head

tail

deqLock

enqLock

size

8

Uh-oh

Read size

Art of Multiprocessor Programming 2828

Dequeuer

head

tail

deqLock

enqLock

size

2

Lock deqLock

Art of Multiprocessor Programming 2929

Dequeuer

head

tail

deqLock

enqLock

size

2

Read sentinel’s next field

OK

Art of Multiprocessor Programming 3030

Dequeuer

head

tail

deqLock

enqLock

size

2

Read value

Art of Multiprocessor Programming 3131

Dequeuer

head

tail

deqLock

enqLock

size

2

Make first Node new sentinel

Art of Multiprocessor Programming 3232

Dequeuer

head

tail

deqLock

enqLock

size

1

Decrement size

Art of Multiprocessor Programming 3333

Dequeuer

head

tail

deqLock

enqLock

size

1Release deqLock

Art of Multiprocessor Programming© Herlihy-Shavit 2007

34

Monitor Locks

• Java ReentrantLocks are monitors• Allow blocking on a condition

rather than spinning• Threads:

– acquire and release lock– wait on a condition

Art of Multiprocessor Programming© Herlihy-Shavit 2007

35

public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit); Condition newCondition(); void unlock;}

The Java Lock Interface

Create condition to wait on

Art of Multiprocessor Programming© Herlihy-Shavit 2007

36

Lock Conditions

public interface Condition { void await(); boolean await(long time, TimeUnit unit); … void signal(); void signalAll(); }

Art of Multiprocessor Programming© Herlihy-Shavit 2007

37

public interface Condition { void await(); boolean await(long time, TimeUnit unit); … void signal(); void signalAll(); }

Lock Conditions

Release lock and wait on condition

Art of Multiprocessor Programming© Herlihy-Shavit 2007

38

public interface Condition { void await(); boolean await(long time, TimeUnit unit); … void signal(); void signalAll(); }

Lock Conditions

Wake up one waiting thread

Art of Multiprocessor Programming© Herlihy-Shavit 2007

39

public interface Condition { void await(); boolean await(long time, TimeUnit unit); … void signal(); void signalAll(); }

Lock Conditions

Wake up all waiting threads

Art of Multiprocessor Programming© Herlihy-Shavit 2007

40

Await

• Releases lock associated with q• Sleeps (gives up processor)• Awakens (resumes running)• Reacquires lock & returns

q.await()

Art of Multiprocessor Programming© Herlihy-Shavit 2007

41

Signal

• Awakens one waiting thread– Which will reacquire lock

q.signal();

Art of Multiprocessor Programming© Herlihy-Shavit 2007

42

Signal All

• Awakens all waiting threads– Which will each reacquire lock

q.signalAll();

Unbounded Lock-Free Queue (Nonblocking)

• Unbounded– No need to count the number of items

• Lock-free– Use AtomicReference<V>

• An object reference that may be updated atomically. 

– boolean compareAndSet(V expect, V update)• Atomically sets the value to the given updated value if

the current value == the expected value.

• Nonblocking– No need to provide conditions on which to wait

43

Art of Multiprocessor Programming 4444

A Lock-Free Queue

Sentinel

head

tail

Art of Multiprocessor Programming 4545

Enqueue

head

tail

Enq( )

Art of Multiprocessor Programming 4646

Enqueue

head

tail

Art of Multiprocessor Programming 4747

Logical Enqueue

head

tail

CAS

Art of Multiprocessor Programming 4848

Physical Enqueue

head

tailCAS

Art of Multiprocessor Programming 4949

Enqueue

• These two steps are not atomic• The tail field refers to either

– Actual last Node (good)– Penultimate Node (not so good)

• Be prepared!

Art of Multiprocessor Programming 5050

Enqueue

• What do you do if you find– A trailing tail?

• Stop and help fix it– If tail node has non-null next field– CAS the queue’s tail field to tail.next

• As in the universal construction

Art of Multiprocessor Programming 5151

When CASs Fail

• During logical enqueue– Abandon hope, restart– Still lock-free (why?)

• During physical enqueue– Ignore it (why?)

Art of Multiprocessor Programming 5252

Dequeuer

head

tail

Read value

Art of Multiprocessor Programming 5353

Dequeuer

head

tail

Make first Node new sentinel

CAS

Art of Multiprocessor Programming© Herlihy-Shavit 2007

54

Unbounded Lock-Free Queue(Nonblocking)

public class LockFreeQueue<T> {

private AtomicReference<Node> head;

private AtomicReference<Node> tail;

public LockFreeQueue() {

this.head = new AtomicReference<Node>(null);

this.tail = new AtomicReference<Node>(null);

}

public class Node {

public T value;

public AtomicReference<Node> next;

public Node(T value) {

this.value = value;

this.next = new AtomicReference<Node>(null);

}

}

55

public void enq(T item) {

Node node = new Node(item);

while (true) {

Node last = tail.get();

Node next = last.next.get();

if (last == tail.get()) {

if (next == null) {

if (last.next.compareAndSet(next, node)) {

tail.compareAndSet(last, node);

return;

}

} else {

tail.compareAndSet(last, next);

}

}

}

}

public T deq() throws EmptyException {

while (true) {

Node first = head.get();

Node last = tail.get();

Node next = first.next.get();

if (first == head.get()) {

if (first == last) {

if (next = null) {

throw new EmptyException();

}

tail.compareAndSet(last, next);

} else {

T value = next.value;

if (head.compareAndSet(first,next))

return value;

}

}

}

}

Unbounded Lock-Free Queue(Nonblocking)

Art of Multiprocessor Programming 5656

Concurrent Stack

• Methods– push(x) – pop()

• Last-in, First-out (LIFO) order• Lock-Free!

Art of Multiprocessor Programming 5757

Empty Stack

Top

Art of Multiprocessor Programming 5858

Push

Top

Art of Multiprocessor Programming 5959

Push

TopCAS

Art of Multiprocessor Programming 6060

Push

Top

Art of Multiprocessor Programming 6161

Push

Top

Art of Multiprocessor Programming 6262

Push

Top

Art of Multiprocessor Programming 6363

Push

TopCAS

Art of Multiprocessor Programming 6464

Push

Top

Art of Multiprocessor Programming 6565

Pop

Top

Art of Multiprocessor Programming 6666

Pop

TopCAS

Art of Multiprocessor Programming 6767

Pop

TopCAS

mine!

Art of Multiprocessor Programming 6868

Pop

TopCAS

Art of Multiprocessor Programming 6969

Pop

Top

Art of Multiprocessor Programming 7070

public class LockFreeStack { private AtomicReference top = new AtomicReference(null); public boolean tryPush(Node node){ Node oldTop = top.get(); node.next = oldTop; return(top.compareAndSet(oldTop, node)) } public void push(T value) { Node node = new Node(value); while (true) { if (tryPush(node)) { return; } else backoff.backoff(); }}

Lock-free Stack

Art of Multiprocessor Programming 7171

public class LockFreeStack { private AtomicReference top = new AtomicReference(null); public Boolean tryPush(Node node){ Node oldTop = top.get(); node.next = oldTop; return(top.compareAndSet(oldTop, node)) }public void push(T value) { Node node = new Node(value); while (true) { if (tryPush(node)) { return; } else backoff.backoff()}}

Lock-free Stack

tryPush attempts to push a node

Art of Multiprocessor Programming 7272

public class LockFreeStack { private AtomicReference top = new AtomicReference(null); public boolean tryPush(Node node){ Node oldTop = top.get(); node.next = oldTop; return(top.compareAndSet(oldTop, node)) } public void push(T value) { Node node = new Node(value); while (true) { if (tryPush(node)) { return; } else backoff.backoff()}}

Lock-free Stack

Read top value

Art of Multiprocessor Programming 7373

public class LockFreeStack { private AtomicReference top = new AtomicReference(null); public boolean tryPush(Node node){ Node oldTop = top.get(); node.next = oldTop; return(top.compareAndSet(oldTop, node)) } public void push(T value) { Node node = new Node(value); while (true) { if (tryPush(node)) { return; } else backoff.backoff()}}

Lock-free Stack

current top will be new node’s successor

Art of Multiprocessor Programming 7474

public class LockFreeStack { private AtomicReference top = new AtomicReference(null); public boolean tryPush(Node node){ Node oldTop = top.get(); node.next = oldTop; return(top.compareAndSet(oldTop, node)) } public void push(T value) { Node node = new Node(value); while (true) { if (tryPush(node)) { return; } else backoff.backoff()}}

Lock-free Stack

Try to swing top, return success or failure

Art of Multiprocessor Programming 7575

public class LockFreeStack { private AtomicReference top = new AtomicReference(null); public boolean tryPush(Node node){ Node oldTop = top.get(); node.next = oldTop; return(top.compareAndSet(oldTop, node)) } public void push(T value) { Node node = new Node(value); while (true) { if (tryPush(node)) { return; } else backoff.backoff()}}

Lock-free Stack

Push calls tryPush

Art of Multiprocessor Programming 7676

public class LockFreeStack { private AtomicReference top = new AtomicReference(null); public boolean tryPush(Node node){ Node oldTop = top.get(); node.next = oldTop; return(top.compareAndSet(oldTop, node)) } public void push(T value) { Node node = new Node(value); while (true) { if (tryPush(node)) { return; } else backoff.backoff()}}

Lock-free Stack

Create new node

Art of Multiprocessor Programming 7777

public class LockFreeStack { private AtomicReference top = new AtomicReference(null); public boolean tryPush(Node node){ Node oldTop = top.get(); node.next = oldTop; return(top.compareAndSet(oldTop, node)) } public void push(T value) { Node node = new Node(value); while (true) { if (tryPush(node)) { return; } else backoff.backoff()}}

Lock-free Stack

If tryPush() fails,back off before retrying

78

protected boolean tryPush(Node node)

{

Node oldTop = top.get();

node.next = oldTop;

return (top.compareAndSet(oldTop, node));

}

public void push( T value )

{

Node node = new Node( value );

while (true) {

if (tryPush(node)) { return; }

else { backoff.backoff( ); }

}

}

protected Node tryPop( ) throws EmptyException

{

Node oldTop = top.get();

if ( oldTop == null ) {

throw new EmptyException( );

}

Node newTop = oldTop.next;

if ( top.compareAndSet( oldTop, newTop ) ) {

return oldTop;

} else { return null; }

}

public T pop() throws EmptyException {

while (true) {

Node returnNode = tryPop( );

if ( returnNode != null ) {

return returnNode.value;

} else { backoff.backoff( ); }

}

}

Unbounded Lock-Free Stack

Art of Multiprocessor Programming 7979

Lock-free Stack

• Good– No locking

• Bad– Without GC, fear ABA– Without backoff, huge contention at

top – In any case, no parallelism

Art of Multiprocessor Programming 8080

Question

• Are stacks inherently sequential?• Reasons why

– Every pop() call fights for top item

• Reasons why not– Think about it!

top related