here

advertisement
Concurrent Queues
Gal Milman
Based on Chapter 10 (Concurrent Queues and the ABA Problem)
in The Art of Multiprocessor Programming by Herlihy and Shavit
Seminar 2 (236802) in Advanced Topics in Concurrent Programming
Winter 15/16
Instructor: Erez Petrank
Queue
Head
Tail
Dequeue
Enqueue
(Get)
(Put)
next
We will review:
• Blocking Queue
• Lock-Free Queue
Q u e u e
o ABA Problem
• Synchronous Dual Queue
Blocking Queue
head
Initialization:
tail = head = Node(value=null)
capacity = input_capacity
size = AtomicInteger(0)
next
tail
enqLock,
deqLock
notFullCondition, notEmptyCondition
head
next
Dummy
node
value
value
next
next
tail
Blocking Queue - enq
head
tail
public void addNode(T x) {
Node newNode = new Node(x);
tail.next = newNode;
tail = newNode;
}
newNode
head
1
x
2
tail
Blocking Queue - deq
head
x
tail
public T removeNodeAndGetValue() {
result = head.next.value;
head = head.next;
return result;
}
head
x
tail
Blocking Queue - enq
public void enq(T x) {
boolean mustWakeDequeuers = false;
enqLock.lock();
while (size.get() == capacity)
notFullCondition.await();
addNode(x);
if (size.getAndIncrement() == 0)
mustWakeDequeuers = true;
enqLock.unlock();
if (mustWakeDequeuers) {
deqLock.lock();
notEmptyCondition.signalAll();
deqLock.unlock();
}
}
Blocking Queue - enq
Why
should void
we lock
beforex)
signaling?
To avoid the lost wakeup problem.
public
enq(T
{
boolean mustWakeDequeuers
enq = false;
enqLock.lock();
deqlock.lock()
while (size.get() == capacity)
while (queue
is empty)
notFullCondition.await();
addNode()
addNode(x);
if (size.getAndIncrement()
== 0)
notEmptyCondition.signalAll()
Why should
mustWakeDequeuers = true;
notEmptyCondition.await()
we lock before
enqLock.unlock();
signaling?
if (mustWakeDequeuers) {
deqLock.lock();
notEmptyCondition.signalAll();
deqLock.unlock();
}
}
deq is symmetrical.
deq
We will review:
performance
• Blocking Queue
progress
• Lock-Free Queue
Q u e u e
o ABA Problem
• Synchronous Dual Queue
Lock-Free Algorithms
In the absence of locks:
•
Use atomic read-modify-write hardware primitives,
like CAS
•
But what about an action that is composed of
several atomic primitive calls?
o It should mark the data structure,
so other threads may finish the action.
"lock"
"unlock"
Lock-Free Queue
Initialization:
Unbounded ⇒ size and capacity are unnecessary
Lock-free ⇒ locks and conditions are unnecessary
head
tail = head = Node(value=null)
next
tail
* head, tail and Node.next are AtomicReference<Node>
Lock-Free Queue - enq
public void enq(T x) {
Node node = new Node(x);
while (true) {
Node last = tail.get();
Node next = last.next.get();
if (next == null) {
if (last.next.compareAndSet(next, node)) {
tail.compareAndSet(last, node);
return;
}
} else {
tail.compareAndSet(last, next);
}
}
}
Lock-Free Queue - deq
public T deq() throws EmptyException {
while (true) {
Node first = head.get();
Node last = tail.get();
Node next = first.next.get();
if (first != last) {
T value = next.value;
if (head.compareAndSet(first, next))
return value;
} else {
if (next == null)
throw new EmptyException();
tail.compareAndSet(last, next);
}
}
}
ABA Problem
• Consider our lock-free queue
• But stop relying on Java’s garbage
collector
• Each thread will maintain a private free list
• Now we encounter the ABA problem
head
a
1
2
deq
b
head
b
x
free list (2)
a
tail
tail
enq(y)
head
b
deq
a
y
tail
deq
a
tail
free list (2)
head
b
head
a
free list (2)
b
deq
tail
ABA Solution: A Stamp
AtomicStampedReference<Node> encapsulates:
• a reference to Node
• an integer stamp
AtomicReference<Node>
replaced
by
AtomicStampedReference<Node>
stamp is incremented in each compareAndSet.
We will review:
• Blocking Queue
• Lock-Free Queue
Q u e u e
o ABA Problem
• Synchronous Dual Queue
Synchronous Dual Queue
reservation
fulfilment
Blocking Queue States
head
vs.
Synchronous Dual Queue States
head
x
enq reservations
x
y
tail
tail
head
head
tail
tail
NotEmpty
Condition
- deq1
- deq2
- …
head
head
deq reservations
null
tail
y
fair
null
tail
In the dual queue - enq and deq are symmetrical.
Both check queue’s state to figure out if they should be a reserver or a fulfiller.
Synchronous Dual Queue
•
Reserver adds an item to the queue,
lock-
•
Other threads might finish the addition
free
then spins on the item
•
Fulfiller changes the item
•
To end the rendezvous - the item is removed from the queue
•
rendezvous
lock-
Other threads might execute the removal
free
Download