lecture 6-2 : concurrent queues and stacks companion slides for the art of multiprocessor...
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!