Lecture 16: Concurrent Linked Lists
Overview
- Lab 04 Tips
-
Set
as a Linked List
- Synchronization Philosophies
- Coarse-grained Linked List
- Fine-grained Linked List
- Optimistic Linked List
Prime Tasks
Suggested structure:
- Compute small primes (up to
Math.sqrt(MAX)
)
- use
Primes.getPrimesUpTo(int max)
- Use small primes and SoE to find rest of primes
- consider a block
[n, n + k]
of numbers from n
to n + k
- for each small prime
p
, remove multiples of p
from [n, n + k]
- remaining numbers are prime
- Write primes from step 2 in order to
primes
array
Have a dedicated thread for step 3; use several threads to perform step 2 in parallel
A Set
A Set
of elements:
- store a collection of distinct elements
-
add
an element
- no effect if element already there
-
remove
an element
- check if set
contains
an element
An 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);
}
Implementing SimpleSet
Store elements in a linked list of nodes
- Each
Node
stores:
- reference to the stored object
- reference to the next
Node
- a key associated with the object
- use hash code of object
- keys can be sorted
- The list stores
- reference to
head
node
- a
tail
node
-
head
and tail
have min and max key values
Our Goals
- Correctness, safety, liveness
- linearizability
- deadlock-freedom
- starvation-freedom?
- nonblocking???
- Performance
Synchronization Philosophies
Synchronization Philosophies
- Coarse-Grained
- lock whole data structure for every operation
- Fine-Grained
- only lock what is needed to avoid disaster
- Optimisitc
- don’t lock anything to read
- lock to modify
- Lazy
- use “logical” removal
- only use locks occasionally
- Nonblocking
Coarse-grained Linked List
Coarse-grained Linked List
One lock for whole data structure
For any operation:
- Lock entire list
- Perform operation
- Unlock list
Add item
with Key 5
Step 1: Lock List
Step 2: Find Correct Location
Step 3: Do Insertion
Step 4: Unlock List
In Code
- Look at implementation
- Test performance
- This is our baseline!
Fine-grained Linked List
One lock per node
For any operation:
- Store
curr
, pred
(initialized to head)
- Hand-over-hand locking:
- lock
curr = pred.next
- unlock
pred
- set
pred = curr
- repeat until correct location found
- Perform operation
- Release locks
Add item
with Key 5
Step 1: Set and Lock pred
Step 2: Hand over Hand Locking
Step 4: Release Locks
Test
- More efficient than coarse?
- What affects performance?
Optimistic Linked List
One lock per node
For any operation:
- Find location
- Acquire locks
- Validate location
- go back to 1 if this fails!
- Perform operation
- Release locks
Add item
with Key 5
Step 1: Find Location
Step 2: Acquire Locks
Step 3: Validate
Step 5: Release Locks
Why Could Validation fail?
Questions
- Liveness: Deadlock-free? Starvation-free?
- Performance: When do you expect good performance? When not?
Next Time:
- Lazy Synchronized List
- Nonblocking Synchronized List
- Other Data Structures