Lecture 31: Optimistic Linked Lists
COSC 273: Parallel and Distributed Computing
Spring 2023
Annoucements
- Leaderboard submission results tomorrow
- Next leaderboard submission is Friday? Monday?
- Take-home quiz: released Wednesday (Gradescope), due Friday
Today
Concurrent Linked Lists, Three Ways:
- Coarse locking
- Fine-grained locking
- Optimistic locking
A Generic Task
Store, access, & modify collection of distinct elements:
The Set
ADT:
-
add
an element
- no effect if element already there
-
remove
an element
- check if set
contains
an element
Java SimpleSet
Interface
public interface SimpleSet<T> {
// Add an element to the SimpleSet. Returns true if the element
// was not already in the set.
boolean add(T x);
// Remove an element from the SimpleSet. Returns true if the
// element was previously in the set.
boolean remove(T x);
// Test if a given element is contained in the set.
boolean contains(T x);
}
Linked List SimpleSet
s
- Each
Node
stores:
- reference to the stored object
- reference to the next
Node
- a numerical key associated with the object
- The list stores
- reference to
head
node
- a
tail
node
-
head
and tail
have min and max key values
- nodes have strictly increasing keys
Why Keys?
Question. Why is it helpful to store keys in increasing order?
Our Goals
- Correctness, safety, liveness
- deadlock-freedom
- starvation-freedom?
- nonblocking??
- linearizability???
- Performance
Synchronization Philosophies
- Coarse-Grained (
CoarseList.java
)
- lock whole data structure for every operation
- Fine-Grained (
FineList.java
)
- only lock what is needed to avoid disaster
- Optimistic (
OptimisticList.java
)
- don’t lock anything to read, only lock to modify
- Lazy (
LazyList.java
)
- use “logical” removal, only lock occasionally
- Nonblocking (
NonblockingList.java
)
Coarse-grained Locking
One lock for whole data structure
For any operation:
- Lock entire list
- Perform operation
- Unlock list
See CoarseList.java
Coarse-grained Insertion
![](/assets/img/concurrent-lists/coarse-list.png)
Step 1: Acquire Lock
![](/assets/img/concurrent-lists/coarse-list-lock.png)
Step 2: Iterate to Find Location
![](/assets/img/concurrent-lists/coarse-list-insert-1.png)
Step 2: Iterate to Find Location
![](/assets/img/concurrent-lists/coarse-list-insert-2.png)
Step 2: Iterate to Find Location
![](/assets/img/concurrent-lists/coarse-list-insert-3.png)
Step 3: Insert Item
![](/assets/img/concurrent-lists/coarse-list-insert-4.png)
Step 4: Unlock List
![](/assets/img/concurrent-lists/coarse-list-insert-5.png)
Coarse-grained Appraisal
Advantages:
- Easy to reason about
- Easy to implement
Disadvantages:
- No parallelism
- All operations are blocking
Fine-grained Locking
One lock per node
For any operation:
- Lock head and its next
- Hand-over-hand locking while searching
- always hold at least one lock
- Perform operation
- Release locks
See FineList.java
A Fine-grained Insertion
![](/assets/img/concurrent-lists/fine-list.png)
Step 1: Lock Initial Nodes
![](/assets/img/concurrent-lists/fine-list-insert-1.png)
Step 2: Hand-over-hand Locking
![](/assets/img/concurrent-lists/fine-list-insert-1.png)
Step 2: Hand-over-hand Locking
![](/assets/img/concurrent-lists/fine-list-insert-2.png)
Step 2: Hand-over-hand Locking
![](/assets/img/concurrent-lists/fine-list-insert-3.png)
Step 4: Unlock Nodes
![](/assets/img/concurrent-lists/fine-list-insert-5.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-1.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-2.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-3.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-4.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-5.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-6.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-7.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-8.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-9.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-10.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-11.png)
An Advantage: Parallel Access
![](/assets/img/concurrent-lists/fine-list-concurrent-12.png)
Fine-grained Appraisal
Advantages:
- Parallel access
- Reasonably simple implementation
Disadvantages:
- More locking overhead
- can be much slower than coarse-grained
- All operations are blocking
Optimistic Synchronization
Fine-grained wastes resources locking
- Nodes are locked when traversed
- Locked even if not modified!
A better procedure?
- Traverse without locking
- Lock relevant nodes
- Perform operation
- Unlock nodes
A Better Way?
![](/assets/img/concurrent-lists/optimistic-list.png)
A Better Way?
![](/assets/img/concurrent-lists/optimistic-insert-1.png)
A Better Way?
![](/assets/img/concurrent-lists/optimistic-insert-2.png)
A Better Way?
![](/assets/img/concurrent-lists/optimistic-insert-3.png)
A Better Way?
![](/assets/img/concurrent-lists/optimistic-insert-4.png)
A Better Way?
![](/assets/img/concurrent-lists/optimistic-insert-8.png)
An Issue!
Between traversing and locking
- Another thread modifies the list
- Now locked nodes aren’t the right nodes!
An Issue, Illustrated
![](/assets/img/concurrent-lists/optimistic-insert-3.png)
An Issue, Illustrated
![](/assets/img/concurrent-lists/optimistic-issue-4.png)
An Issue, Illustrated
![](/assets/img/concurrent-lists/optimistic-issue-5.png)
An Issue, Illustrated
![](/assets/img/concurrent-lists/optimistic-issue-6.png)
An Issue, Illustrated
![](/assets/img/concurrent-lists/optimistic-issue-8.png)
An Issue, Illustrated
![](/assets/img/concurrent-lists/optimistic-issue-9.png)
How can we Address this Issue?
Optimistic Synchronization, Validated
- Traverse without locking
- Lock relevant nodes
-
Validate list
- if validation fails, go back to Step 1
- Perform operation
- Unlock nodes
Seet OptimisticList.java
How do we Validate?
After locking, ensure that:
-
pred
is reachable from head
-
curr
is pred
’s successor
If these conditions aren’t met:
Optimistic Insertion
![](/assets/img/concurrent-lists/optimistic-list.png)
Step 1: Traverse the List
![](/assets/img/concurrent-lists/optimistic-insert-1.png)
Step 1: Traverse the List
![](/assets/img/concurrent-lists/optimistic-insert-2.png)
Step 1: Traverse the List
![](/assets/img/concurrent-lists/optimistic-insert-3.png)
Step 2: Acquire Locks
![](/assets/img/concurrent-lists/optimistic-insert-4.png)
Step 3: Validate List - Traverse
![](/assets/img/concurrent-lists/optimistic-insert-5.png)
Step 3: Validate List - pred
Reachable?
![](/assets/img/concurrent-lists/optimistic-insert-6.png)
Step 3: Validate List - Is curr
next?
![](/assets/img/concurrent-lists/optimistic-insert-7.png)
Step 5: Release Locks
![](/assets/img/concurrent-lists/optimistic-insert-9.png)
Implementing Validation
private boolean validate (Node pred, Node curr) {
Node node = head;
while (node.key <= pred.key) {
if (node == pred) {
return pred.next == curr;
}
node = node.next;
}
return false;
}
Optimistic Appraisal
Advantages:
- Less locking than fine-grained
- More opportunities for parallelism than coarse-grained
Disadvantages:
- Validation could fail
- Not starvation-free
- even if locks are starvation-free
Time v. Threads, 8 Elements
![](/assets/img/2023s-cosc-273/linked-lists/threads-vs-time-small-optimistic.svg)
Time v. Threads, 8,192 Elements
![](/assets/img/2023s-cosc-273/linked-lists/threads-vs-time-large-optimistic.svg)
Coarse Time v. Threads
![](/assets/img/2023s-cosc-273/linked-lists/coarse-time-vs-threads.svg)
Fine Time v. Threads
![](/assets/img/2023s-cosc-273/linked-lists/fine-time-vs-threads.svg)
Optimistic Time v. Threads
![](/assets/img/2023s-cosc-273/linked-lists/optimistic-time-vs-threads.svg)
Next Time
Another Way: Lazy Synchronization
- don’t modify the list unless you really have to