Concurrent Data Structures (Concurrent Queue/Stack)

Download Report

Transcript Concurrent Data Structures (Concurrent Queue/Stack)

Lecture 6-2 :
Concurrent Queues and Stacks
Companion slides for
The Art of Multiprocessor
Programming
by 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()
public interface Pool<T> {
void put(T item);
T get();
}
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
2
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
3
Bounded vs Unbounded
• Bounded
– Fixed capacity
– Good when resources an issue
• Unbounded
– Holds any number of objects
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
4
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
5
Queue: Concurrency
enq(x)
tail
head y=deq()
enq() and deq()
work at
different ends
of the object
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
6
Concurrency
y=deq()
enq(x)
Challenge: what
if the queue is
empty or full?
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
7
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;
}
}
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
8
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;
}
9
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
enqueue
1.
2.
3.
4.
5.
6.
7.
8.
Acquires enqLock
Reads the size field
If full, enqueuer must wait until dequeuer makes room
enqueuer waits on notFullCondition field, releasing enqLock
temporarily, and blocking until that condition is signaled.
Each time the thread awakens, it checks whether there is a
room, and if not, goes back to sleep
Insert new item into tail
Release enqLock
If queue was empty, notify/signal waiting dequeuers
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
11
dequeue
1.
2.
3.
4.
5.
6.
7.
8.
9.
Acquires deqLock
Reads the size field
If empty, dequeuer must wait until item is enqueued
dequeuer waits on notEmptyCondition field, releasing
deqLock temporarily, and blocking until that condition is
signaled.
Each time the thread awakens, it checks whether item was
enqueued, and if not, goes back to sleep
Assigne the value of head’s next node to “result” and reset
head to head’s next node
Release deqLock
If queue was full, notify/signal waiting enqueuers
Return “result”
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
12
Bounded Queue
head
tail
Sentinel
Art of Multiprocessor Programming
13
Bounded Queue
head
tail
First actual item
Art of Multiprocessor Programming
14
Bounded Queue
head
tail
deqLock
Lock out
other deq()
calls
Art of Multiprocessor Programming
15
Bounded Queue
head
tail
deqLock
enqLock
Lock out
other enq()
calls
Art of Multiprocessor Programming
16
Not Done Yet
head
tail
deqLock
enqLock
Need to tell
whether queue
is full or
empty
Art of Multiprocessor Programming
17
Not Done Yet
head
tail
deqLock
enqLock
size
1
Max size is 8 items
Art of Multiprocessor Programming
18
Not Done Yet
head
tail
deqLock
enqLock
size
1
Incremented by enq()
Decremented by deq()
Art of Multiprocessor Programming
19
Enqueuer
head
tail
deqLock
enqLock
Lock enqLock
size
1
Art of Multiprocessor Programming
20
Enqueuer
head
tail
deqLock
enqLock
Read size
size
1
OK
Art of Multiprocessor Programming
21
Enqueuer
head
tail
deqLock
No need
to lock
tail
enqLock
size
1
Art of Multiprocessor Programming
22
Enqueuer
head
tail
deqLock
enqLock
Enqueue Node
size
1
Art of Multiprocessor Programming
23
Enqueuer
head
tail
deqLock
enqLock
size
2
1
getAndincrement()
Art of Multiprocessor Programming
24
Enqueuer
head
tail
deqLock
enqLock
size
Release lock
8
2
Art of Multiprocessor Programming
25
Enqueuer
head
tail
deqLock
enqLock
size
2
If queue was
empty, notify
waiting dequeuers
Art of Multiprocessor Programming
26
Unsuccesful Enqueuer
…
head
tail
deqLock
enqLock
Read size
size
8
Uhoh
Art of Multiprocessor Programming
27
Dequeuer
head
tail
deqLock
enqLock
size
Lock deqLock
2
Art of Multiprocessor Programming
28
Dequeuer
head
tail
deqLock
enqLock
size
2
Read
sentinel’s
next field
OK
Art of Multiprocessor Programming
29
Dequeuer
head
tail
deqLock
enqLock
Read value
size
2
Art of Multiprocessor Programming
30
Dequeuer
Make first
Node new
sentinel
head
tail
deqLock
enqLock
size
2
Art of Multiprocessor Programming
31
Dequeuer
head
tail
deqLock
enqLock
size
Decrement
size
1
Art of Multiprocessor Programming
32
Dequeuer
head
tail
deqLock
enqLock
size
1
Release
deqLock
Art of Multiprocessor Programming
33
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
34
The Java Lock Interface
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit);
Condition newCondition();
void unlock;
}
Create condition to wait on
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
35
Lock Conditions
public interface Condition {
void await();
boolean await(long time, TimeUnit unit);
…
void signal();
void signalAll();
}
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();
Release lock and
}
wait on condition
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
37
Lock Conditions
public interface Condition {
void await();
boolean await(long time, TimeUnit unit);
…
void signal();
void signalAll();
}
Wake up one waiting thread
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
38
Lock Conditions
public interface Condition {
void await();
boolean await(long time, TimeUnit unit);
…
void signal();
void signalAll();
}
Wake up all waiting threads
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
39
Await
q.await()
•
•
•
•
Releases lock associated with q
Sleeps (gives up processor)
Awakens (resumes running)
Reacquires lock & returns
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
40
Signal
q.signal();
• Awakens one waiting thread
– Which will reacquire lock
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
41
Signal All
q.signalAll();
• Awakens all waiting threads
– Which will each reacquire lock
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
42
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
A Lock-Free Queue
head
tail
Sentinel
Art of Multiprocessor Programming
44
Enqueue
head
tail
Enq(
Art of Multiprocessor Programming
)
45
Enqueue
head
tail
Art of Multiprocessor Programming
46
Logical Enqueue
CAS
head
tail
Art of Multiprocessor Programming
47
Physical Enqueue
head
tail
CAS
Art of Multiprocessor Programming
48
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
49
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
50
When CASs Fail
• During logical enqueue
– Abandon hope, restart
– Still lock-free (why?)
• During physical enqueue
– Ignore it (why?)
Art of Multiprocessor Programming
51
Dequeuer
head
tail
Read value
Art of Multiprocessor Programming
52
Dequeuer
Make first
Node new
sentinel
CAS
head
tail
Art of Multiprocessor Programming
53
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);
}
}
Art of Multiprocessor Programming©
Herlihy-Shavit 2007
54
Unbounded Lock-Free Queue
(Nonblocking)
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;
}
}
}
}
55
Concurrent Stack
• Methods
– push(x)
– pop()
• Last-in, First-out (LIFO) order
• Lock-Free!
Art of Multiprocessor Programming
56
Empty Stack
Top
Art of Multiprocessor Programming
57
Push
Top
Art of Multiprocessor Programming
58
Push
Top
CAS
Art of Multiprocessor Programming
59
Push
Top
Art of Multiprocessor Programming
60
Push
Top
Art of Multiprocessor Programming
61
Push
Top
Art of Multiprocessor Programming
62
Push
Top
CAS
Art of Multiprocessor Programming
63
Push
Top
Art of Multiprocessor Programming
64
Pop
Top
Art of Multiprocessor Programming
65
Pop
Top
CAS
Art of Multiprocessor Programming
66
Pop
Top
CAS
mine
!
Art of Multiprocessor Programming
67
Pop
Top
CAS
Art of Multiprocessor Programming
68
Pop
Top
Art of Multiprocessor Programming
69
Lock-free Stack
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();
}}
Art of Multiprocessor Programming
70
Lock-free Stack
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 attempts
backoff.backoff()
tryPush
to push a node
}}
Art of Multiprocessor Programming
71
Lock-free Stack
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; Read top value
} else backoff.backoff()
}}
Art of Multiprocessor Programming
72
Lock-free Stack
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
current
topbackoff.backoff()
will be new node’s successor
}}
Art of Multiprocessor Programming
73
ry
Lock-free Stack
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;
} elsetop,
backoff.backoff()
to swing
return success or failure
}}
Art of Multiprocessor Programming
74
Lock-free Stack
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()
Push calls tryPush
}}
Art of Multiprocessor Programming
75
Lock-free Stack
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()
Create new node
}}
Art of Multiprocessor Programming
76
Lock-free Stack
public class LockFreeStack {
private AtomicReference top = new
AtomicReference(null);
public boolean tryPush(Node node){
Node oldTop
top.get();
If= tryPush()
fails,
node.next = oldTop;
back off before node))
return(top.compareAndSet(oldTop,
}
retrying
public void push(T value) {
Node node = new Node(value);
while (true) {
if (tryPush(node)) {
return;
} else backoff.backoff()
}}
Art of Multiprocessor Programming
77
Unbounded Lock-Free Stack
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( ); }
}
}
78
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
79
Question
• Are stacks inherently sequential?
• Reasons why
– Every pop() call fights for top item
• Reasons why not
– Think about it!
Art of Multiprocessor Programming
80