• No results found

Parallel linked lists

N/A
N/A
Protected

Academic year: 2021

Share "Parallel linked lists"

Copied!
239
0
0

Loading.... (view fulltext now)

Full text

(1)

Parallel linked lists

Lecture 10 of TDA384/DIT391

Principles of Concurrent Programming

Sandro Stucki

Chalmers University of Technology | University of Gothenburg SP3 2018/2019

(2)

Today’s menu

The burden of locking Linked set implementations

Nodes, lists, and sets Sequential access Parallel linked sets

Coarse-grained locking Fine-grained locking Optimistic locking Lazy node removal Lock-free access

(3)
(4)

Synchronization costs

A number of factorschallengedesigning correct and efficient parallelizations:

• sequential dependencies • synchronization costs • spawning costs

• error proneness and composability

Inthis class, we focus on reducing thesynchronization costs associated withlocking.

(5)

Synchronization costs

A number of factorschallengedesigning correct and efficient parallelizations:

• sequential dependencies • synchronization costs • spawning costs

• error proneness and composability

Inthis class, we focus on reducing thesynchronization costs associated withlocking.

(6)

The trouble with locks

Standard techniques for concurrent programming are ultimately based onlocks. Programming with locks has severaldrawbacks:

• performance overhead

• lock granularity is hard to choose: • not enough locking: race conditions • too much locking: not enough parallelism • risk of deadlock and starvation

• lock-based implementations do not compose

• lock-based programs are hard to maintain and modify

Message-passingprogramming is higher-level, but it also inevitably incurssynchronization costs– of magnitude comparable to those associated with locks.

(7)

Breaking free of locks

Lock-free programmingtakes a fresh look at the problems of concurrency and tries to dispense with using locks altogether.

• Lock-based

program-ming ispessimistic: be prepared for the worst possible conditions: if things can gowrong, they will.

• Lock-free programming isoptimistic: do what you have to do without worrying about race conditions:

if things go wrong, justtry again.

(8)

Lock-free programming

Lock-free programming relies on:

• usingstronger primitivesfor atomic access,

• buildingoptimisticalgorithms using those primitives.

Compare-and-set operations are an example of stronger primitives:

public class AtomicInteger {

// atomically set to ‘update’ if current value is ‘expect’ // otherwise do not change value and return false

boolean compareAndSet(int expect, int update) }

To update anAtomicIntegervariablek:

do { // keep trying until no one changes k in between int oldValue = k.get();

int newValue = compute(oldValue);

} while (!k.compareAndSet(oldValue, newValue));

(9)

Compare-and-set is not free

Diagram byAvadlam3,Wikipedia (2016).

CAS operations arenot free: they involve memory barrier operations to synchronize caches(∼100-1000 cycles).

(10)

Compare-and-set is not free

Chart byayshen, based on Peter Norvig’s“Teach Yourself Programming in Ten Years”.

CAS operations arenot free: they involve memory barrier operations to synchronize caches(∼100-1000 cycles).

(11)

Lock-free vs. wait-free

Twoclassesof lock-free algorithms, collectively callednon-blocking: lock-free: guarantee system-wide progress: infinitely often, some

process makes progress,

wait-free: guarantee per-process progress: every process eventually makes progress.

Wait-free isstrongerthan lock-free:

• Lock-free algorithms are free from deadlock.

• Wait-free algorithms are free from deadlock and starvation.

(12)

Thread-safe data structures

Programming correctly without using locks is challenging. Instead of trying to develop general techniques, we focus on implementingreusable data structuresthat make minimal usage of locking. The effort involved in developing correct implementations pays off since very many applications can then use suchthread-safe data structureimplementations to synchronizesafelyandimplicitlyby accessing the structures through their APIs.

A data structure isthread safeif its operations are free from race conditions when executed by multi-threaded clients.

Our lock-free and wait-free algorithms are some of those used in the implementations ofthread safestructures injava.util.concurrent

(non-blocking data structuresatomically accessible in parallel).

(13)
(14)

Parallel linked lists

In the rest of this class, we go through several implementations of linked liststhat supportparallel access; the implementations differ in how much locking they use to guarantee correctness and,

correspondingly, in how much parallelism they allow.

We will usepseudo-codethat is very close to regular Java syntax but occasionally takes some liberties to simplify the notation. On the course website you can download fully working implementations of some of the classes.

(15)

Linked set implementations

Nodes, lists, and sets

(16)

The interface of a set

We use linked lists to implement asetdata structure with interface:

public interface Set<T>

{

// add ‘item’ to set; return false if ‘item’ is already in the set boolean add(T item);

// remove ‘item’ from set; return false if ‘item’ not in the set boolean remove(T item);

// is ‘item’ in set? boolean has(T item); }

(17)

Nodes

The underlying implementations of sets usesingly-linked lists, which are made of chains of nodes. Everynode:

• stores anitem– itsvalue

• has a uniquekey– the value’shash code • points to thenextnode in the chain

In the graphical representations of nodes, we do not distinguish between items and their keys – and represent both by characters:

interface Node<T>

{

// value of node T item();

// hash code of value int key();

// next node in chain Node<T> next(); }

x

value/item/key

next node

(18)

Lists as chains of nodes

A list with specialheadandtailnodes implements a set: • theelementsof the set are items in different nodes • to facilitate searching, the nodes are maintainedsortedin

ascendingkeys

• to facilitate searching, the head has the smallest possible key, the tail has the largest possible key, and all elements havefinitely manykeys that are in between

For example, theset {b,e,a,f,g}is implemented by:

head a b e f g tail

Relaxing these assumptions is possible at the cost of complicating the implementations a bit.

(19)

Linked set implementations

Sequential access

(20)

Sequential set: basic linked implementation

We start with a standard linked-list-based implementation of sets, which only works forsequential access.

class SequentialSet<T> implements Set<T>

{

// nodes at beginning and end

protected Node<T> head, tail;

// empty set

public SequentialSet() {

head = new SequentialNode<>(Integer.MIN_VALUE); // smallest key tail = new SequentialNode<>(Integer.MAX_VALUE); // largest key head.setNext(tail);

}

Emptyset: head tail

(21)

Nodes in a sequential set

A node’s implementation usesprivate attributeswithgettersand setters; this is a bit tedious now (we could just let the set

implementations access the attributes directly), but it will lead to nicer designs in the several variants of set implementations we’ll describe.

class SequentialNode<T> implements Node<T> {

private T item; // value stored in node

private int key; // hash code of item

private Node<T> next; // next node in chain

// getters

T item() { return item; }

int key() { return key; }

Node<T> next() { return next; }

// setters

void setItem(T item) { this.item = item; } void setKey(int key) { this.key = key; } void setNext(Node<T> next) { this.next = next; }

(22)

Finding a position inside a list

Since we maintain nodes in order of key, and every item has a unique key, we cansearchfor the position of any given key by going through the list from head to tail.

The methodfindimplements this frequently used operation of finding thepositionof a key inside a list. The position ofkeyis apair

(pred, curr)ofadjacent nodes, such that pred.key() < key <= curr.key().

For example, the position ofcin the following list is:

head a b e f g tail

pred curr

Thanks to the boundary keys chosen for head and tail, searching for any value keyreturns avalid positionin the list.

(23)

Finding a position inside a list

Since we maintain nodes in order of key, and every item has a unique key, we cansearchfor the position of any given key by going through the list from head to tail.

The methodfindimplements this frequently used operation of finding thepositionof a key inside a list. The position ofkeyis apair

(pred, curr)ofadjacent nodes, such that pred.key() < key <= curr.key().

For example, the position ofcin the following list is:

head a b e f g tail

pred curr

Thanks to the boundary keys chosen for head and tail, searching for any value keyreturns avalid positionin the list.

(24)

Finding a position inside a list

head a b e f g tail

curr

pred currpred currpred curr

// first position from ‘start’ whose key is no smaller than ‘key’

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration curr = start; // from start node

do {

pred = curr; curr = curr.next(); // move to next node

} while (curr.key() < key); // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(25)

Finding a position inside a list

head a b e f g tail

curr

pred curr

pred currpred curr

// first position from ‘start’ whose key is no smaller than ‘key’

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration curr = start; // from start node

do {

pred = curr; curr = curr.next(); // move to next node

} while (curr.key() < key); // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(26)

Finding a position inside a list

head a b e f g tail curr pred curr pred curr pred curr

// first position from ‘start’ whose key is no smaller than ‘key’

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration curr = start; // from start node

do {

pred = curr; curr = curr.next(); // move to next node

} while (curr.key() < key); // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(27)

Finding a position inside a list

head a b e f g tail

curr

pred currpred curr

pred curr

// first position from ‘start’ whose key is no smaller than ‘key’

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration curr = start; // from start node

do {

pred = curr; curr = curr.next(); // move to next node

} while (curr.key() < key); // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(28)

Sequential set: method

has

A sethasitemif and only ifitemis (equal to) the first element in the set whose key is greater than or equal toitem’s.

head a b e f g tail

head a b e f g tail

pred curr

// is ‘item’ in set?

public boolean has(T item) {

int key = item.key(); // item’s key // find position of key from head

Node<T> pred, curr = find(head, key); // curr.key() >= key

return curr.key() == key; // item can only appear here!

}

(29)

Sequential set: method

has

A sethasitemif and only ifitemis (equal to) the first element in the set whose key is greater than or equal toitem’s.

head a b e f g tail

pred curr

// is ‘item’ in set?

public boolean has(T item) {

int key = item.key(); // item’s key // find position of key from head

Node<T> pred, curr = find(head, key); // curr.key() >= key

return curr.key() == key; // item can only appear here!

}

(30)

Sequential set: method

add

A newitemmust be addedbetweenpredandcurr, where (pred, curr)isitem’spositionin the list.

head a b e f g tail

pred curr

node: c

public boolean add(T item) {

Node<T> node = new Node<>(item); // new node

Node<T> pred, curr = find(head, item.key()); // curr.key >= item.key()

if (curr.key() == item.key()) return false; // item already in set

else // item not already in set: add node between pred and curr { node.setNext(curr); pred.setNext(node); return true; } }

(31)

Sequential set: method

add

A newitemmust be addedbetweenpredandcurr, where (pred, curr)isitem’spositionin the list.

head a b e f g tail

pred curr

node: c

public boolean add(T item) {

Node<T> node = new Node<>(item); // new node

Node<T> pred, curr = find(head, item.key()); // curr.key >= item.key()

if (curr.key() == item.key()) return false; // item already in set

else // item not already in set: add node between pred and curr { node.setNext(curr); pred.setNext(node); return true; } }

(32)

Sequential set: method

add

A newitemmust be addedbetweenpredandcurr, where (pred, curr)isitem’spositionin the list.

head a b e f g tail

pred curr

node: c

public boolean add(T item) {

Node<T> node = new Node<>(item); // new node

Node<T> pred, curr = find(head, item.key()); // curr.key >= item.key()

if (curr.key() == item.key()) return false; // item already in set

else // item not already in set: add node between pred and curr { node.setNext(curr); pred.setNext(node); return true; } }

(33)

Sequential set: method

add

A newitemmust be addedbetweenpredandcurr, where (pred, curr)isitem’spositionin the list.

head a b e f g tail

pred curr

node: c

public boolean add(T item) {

Node<T> node = new Node<>(item); // new node

Node<T> pred, curr = find(head, item.key()); // curr.key >= item.key()

if (curr.key() == item.key()) return false; // item already in set

else // item not already in set: add node between pred and curr { node.setNext(curr); pred.setNext(node); return true; } }

(34)

Sequential set: method

add

A newitemmust be addedbetweenpredandcurr, where (pred, curr)isitem’spositionin the list.

head a b e f g tail

pred curr

node: c

public boolean add(T item) {

Node<T> node = new Node<>(item); // new node

Node<T> pred, curr = find(head, item.key()); // curr.key >= item.key()

if (curr.key() == item.key()) return false; // item already in set

else // item not already in set: add node between pred and curr { node.setNext(curr); pred.setNext(node); return true; } }

(35)

Sequential set: method

remove

An elementitemisremovedfrom a set by redirectingpred.nextto skip overcurr, where(pred, curr)isitem’spositionin the list.

head a b e f g tail

pred curr

public boolean remove(T item) {

Node<T> pred, curr = find(head, item.key()); // curr.key() >= item.key()

if (curr.key() > item.key()) return false; // item not in set

else // item in set: remove node curr

{ pred.setNext(curr.next()); return true; } }

(36)

Sequential set: method

remove

An elementitemisremovedfrom a set by redirectingpred.nextto skip overcurr, where(pred, curr)isitem’spositionin the list.

head a b e f g tail

pred curr

public boolean remove(T item) {

Node<T> pred, curr = find(head, item.key()); // curr.key() >= item.key()

if (curr.key() > item.key()) return false; // item not in set

else // item in set: remove node curr

{ pred.setNext(curr.next()); return true; } }

(37)

Sequential set: method

remove

An elementitemisremovedfrom a set by redirectingpred.nextto skip overcurr, where(pred, curr)isitem’spositionin the list.

head a b e f g tail

pred curr

public boolean remove(T item) {

Node<T> pred, curr = find(head, item.key()); // curr.key() >= item.key()

if (curr.key() > item.key()) return false; // item not in set

else // item in set: remove node curr

{ pred.setNext(curr.next()); return true; } }

(38)

Sequential set: method

remove

An elementitemisremovedfrom a set by redirectingpred.nextto skip overcurr, where(pred, curr)isitem’spositionin the list.

head a b e f g tail

pred curr

public boolean remove(T item) {

Node<T> pred, curr = find(head, item.key()); // curr.key() >= item.key()

if (curr.key() > item.key()) return false; // item not in set

else // item in set: remove node curr

{ pred.setNext(curr.next()); return true; } }

(39)

Sequential set: method

remove

An elementitemisremovedfrom a set by redirectingpred.nextto skip overcurr, where(pred, curr)isitem’spositionin the list.

head a b f g tail

pred curr

public boolean remove(T item) {

Node<T> pred, curr = find(head, item.key()); // curr.key() >= item.key()

if (curr.key() > item.key()) return false; // item not in set

else // item in set: remove node curr

{ pred.setNext(curr.next()); return true; } }

(40)

Sequential set does not work under concurrency

Ifmultiple threadsare active on the same instance ofSequentialSet, they can easilyinterferewith each other’s operations – and possibly leave the set in an inconsistent state.

For example, ifthread t runsremove(e)whilethread urunsadd(c):

head a b e f g tail

pred curr

pred curr c

Iffindgoes through the list while another thread is modifying it, even more subtle errors may occur.

(41)

Sequential set does not work under concurrency

Ifmultiple threadsare active on the same instance ofSequentialSet, they can easilyinterferewith each other’s operations – and possibly leave the set in an inconsistent state.

For example, ifthread t runsremove(e)whilethread urunsadd(c): in some interleavings,removeis reverted:

head a b e f g tail

pred curr

pred curr c

Iffindgoes through the list while another thread is modifying it, even more subtle errors may occur.

(42)

Sequential set does not work under concurrency

Ifmultiple threadsare active on the same instance ofSequentialSet, they can easilyinterferewith each other’s operations – and possibly leave the set in an inconsistent state.

For example, ifthread t runsremove(e)whilethread urunsadd(c): in some interleavings,removeis reverted:

head a b e f g tail

pred curr

pred curr c

Iffindgoes through the list while another thread is modifying it, even more subtle errors may occur.

(43)

Sequential set does not work under concurrency

Ifmultiple threadsare active on the same instance ofSequentialSet, they can easilyinterferewith each other’s operations – and possibly leave the set in an inconsistent state.

For example, ifthread t runsremove(e)whilethread urunsadd(c): in some interleavings,removeis reverted:

head a b e f g tail

pred curr

pred curr c

Iffindgoes through the list while another thread is modifying it, even more subtle errors may occur.

(44)

Sequential set does not work under concurrency

Ifmultiple threadsare active on the same instance ofSequentialSet, they can easilyinterferewith each other’s operations – and possibly leave the set in an inconsistent state.

For example, ifthread t runsremove(e)whilethread urunsadd(c): in some interleavings,addis reverted:

head a b e f g tail

pred curr

pred curr c

Iffindgoes through the list while another thread is modifying it, even more subtle errors may occur.

(45)

Sequential set does not work under concurrency

Ifmultiple threadsare active on the same instance ofSequentialSet, they can easilyinterferewith each other’s operations – and possibly leave the set in an inconsistent state.

For example, ifthread t runsremove(e)whilethread urunsadd(c): in some interleavings,addis reverted:

head a b e f g tail

pred curr

pred curr c

Iffindgoes through the list while another thread is modifying it, even more subtle errors may occur.

(46)

Sequential set does not work under concurrency

Ifmultiple threadsare active on the same instance ofSequentialSet, they can easilyinterferewith each other’s operations – and possibly leave the set in an inconsistent state.

For example, ifthread t runsremove(e)whilethread urunsadd(c): in some interleavings,addis reverted:

head a b e f g tail

pred curr

pred curr c

Iffindgoes through the list while another thread is modifying it, even more subtle errors may occur.

(47)

Sequential set does not work under concurrency

Ifmultiple threadsare active on the same instance ofSequentialSet, they can easilyinterferewith each other’s operations – and possibly leave the set in an inconsistent state.

For example, ifthread t runsremove(e)whilethread urunsadd(c): in some interleavings,addis reverted:

head a b e f g tail

pred curr

pred curr c

Iffindgoes through the list while another thread is modifying it, even

(48)
(49)

Parallel linked sets

Coarse-grained locking

(50)

Concurrent set with coarse-grained locking

A straightforward way to makeSequentialSetwork correctly under concurrency is using alockto ensure thatat most one threadat a time is operating on the structure.

class CoarseSet<T> extends SequentialSet<T>

{

// lock controlling access to the whole set

private Lock lock = new ReentrantLock();

// overriding of add, remove, and has

Every methodadd,remove, andhassimply works as follows: 1. acquires thelockon the set

2. performs theoperationas inSequentialSet 3. releases thelockon the set

(51)

Coarse-locking set: method

add

head a b e f g tail

pred curr

node: c

public boolean add(T item) {

lock.lock(); // lock whole set

try {

return super.add(item); // execute ‘add’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(52)

Coarse-locking set: method

add head µ a µ b µ e µ f µ g µ tail µ pred curr node: c

public boolean add(T item) {

lock.lock(); // lock whole set

try {

return super.add(item); // execute ‘add’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(53)

Coarse-locking set: method

add head µ a µ b µ e µ f µ g µ tail µ pred curr node: c

public boolean add(T item) {

lock.lock(); // lock whole set

try {

return super.add(item); // execute ‘add’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(54)

Coarse-locking set: method

add head µ a µ b µ e µ f µ g µ tail µ pred curr node: c

public boolean add(T item) {

lock.lock(); // lock whole set

try {

return super.add(item); // execute ‘add’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(55)

Coarse-locking set: method

add head µ a µ b µ e µ f µ g µ tail µ pred curr node: c

public boolean add(T item) {

lock.lock(); // lock whole set

try {

return super.add(item); // execute ‘add’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(56)

Coarse-locking set: method

add

head a b e f g tail

pred curr

node: c

public boolean add(T item) {

lock.lock(); // lock whole set

try {

return super.add(item); // execute ‘add’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(57)

Coarse-locking set: method

add

head a b e f g tail

pred curr

node: c

public boolean add(T item) {

lock.lock(); // lock whole set

try {

return super.add(item); // execute ‘add’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(58)

Coarse-locking set: method

remove

head a b e f g tail

pred curr

public boolean remove(T item) {

lock.lock(); // lock whole set

try {

return super.remove(item); // execute ‘remove’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(59)

Coarse-locking set: method

remove head µ a µ b µ e µ f µ g µ tail µ pred curr

public boolean remove(T item) {

lock.lock(); // lock whole set

try {

return super.remove(item); // execute ‘remove’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(60)

Coarse-locking set: method

remove head µ a µ b µ e µ f µ g µ tail µ pred curr

public boolean remove(T item) {

lock.lock(); // lock whole set

try {

return super.remove(item); // execute ‘remove’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(61)

Coarse-locking set: method

remove head µ a µ b µ e f µ g µ tail µ pred curr

public boolean remove(T item) {

lock.lock(); // lock whole set

try {

return super.remove(item); // execute ‘remove’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(62)

Coarse-locking set: method

remove head µ a µ b µ f µ g µ tail µ pred curr

public boolean remove(T item) {

lock.lock(); // lock whole set

try {

return super.remove(item); // execute ‘remove’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(63)

Coarse-locking set: method

remove

head a b f g tail

pred curr

public boolean remove(T item) {

lock.lock(); // lock whole set

try {

return super.remove(item); // execute ‘remove’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(64)

Coarse-locking set: method

has

head a b e f g tail

pred curr pred curr

public boolean has(T item) {

lock.lock(); //lock whole set

try {

return super.has(item); // execute ‘has’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(65)

Coarse-locking set: method

has head µ a µ b µ e µ f µ g µ tail µ pred curr pred curr

public boolean has(T item) {

lock.lock(); //lock whole set

try {

return super.has(item); // execute ‘has’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(66)

Coarse-locking set: method

has head µ a µ b µ e µ f µ g µ tail µ pred curr pred curr

public boolean has(T item) {

lock.lock(); //lock whole set

try {

return super.has(item); // execute ‘has’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(67)

Coarse-locking set: method

has head µ a µ b µ e µ f µ g µ tail µ pred curr pred curr

public boolean has(T item) {

lock.lock(); //lock whole set

try {

return super.has(item); // execute ‘has’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(68)

Coarse-locking set: method

has

head a b e f g tail

pred curr

pred curr

public boolean has(T item) {

lock.lock(); //lock whole set

try {

return super.has(item); // execute ‘has’ while locking

} finally {

lock.unlock(); // done: release lock }

}

(69)

Coarse-locking set: pros and cons

Pros:

• obviously correct – it avoids race conditions and deadlocks • if the lock is fair, so is access to the set

• if contention is low (not many threads accessing the set concurrently),CoarseSetis quite efficient

Cons:

• access to the set is essentially sequential – missing opportunities for parallelization

• if contention is high (many threads accessing the set concurrently),CoarseSetis quite slow

(70)

Locking after finding?

Can we reduce the size of the critical sections by executingfind without locking, and then acquiring the lock only before modifying the list?No, because the list may be modified between when a thread performsfindand when it acquires the lock.

For example, supposethread t runsremove(e)whilethread uruns add(c), andtacquires the lock first:

head a b e f g tail

pred curr

pred curr

c

(71)

Locking after finding?

Can we reduce the size of the critical sections by executingfind without locking, and then acquiring the lock only before modifying the list?No, because the list may be modified between when a thread performsfindand when it acquires the lock.

For example, supposethread t runsremove(e)whilethread uruns add(c), andtacquires the lock first:

head µ a µ b µ e µ f µ g µ tail µ pred curr pred curr c 29 / 76

(72)

Locking after finding?

Can we reduce the size of the critical sections by executingfind without locking, and then acquiring the lock only before modifying the list?No, because the list may be modified between when a thread performsfindand when it acquires the lock.

For example, supposethread t runsremove(e)whilethread uruns add(c), andtacquires the lock first:

head µ a µ b µ e µ f µ g µ tail µ pred curr pred curr c 29 / 76

(73)

Locking after finding?

Can we reduce the size of the critical sections by executingfind without locking, and then acquiring the lock only before modifying the list?No, because the list may be modified between when a thread performsfindand when it acquires the lock.

For example, supposethread t runsremove(e)whilethread uruns add(c), andtacquires the lock first:

head µ a µ b µ e µ f µ g µ tail µ pred curr pred curr c 29 / 76

(74)

Locking after finding?

Can we reduce the size of the critical sections by executingfind without locking, and then acquiring the lock only before modifying the list?No, because the list may be modified between when a thread performsfindand when it acquires the lock.

For example, supposethread t runsremove(e)whilethread uruns add(c), andtacquires the lock first:

head a b e f g tail

pred curr

pred curr c

(75)

Locking after finding?

Can we reduce the size of the critical sections by executingfind without locking, and then acquiring the lock only before modifying the list?No, because the list may be modified between when a thread performsfindand when it acquires the lock.

For example, supposethread t runsremove(e)whilethread uruns add(c), andtacquires the lock first:

head µ a µ b µ e µ f µ g µ tail µ pred curr pred curr c 29 / 76

(76)

Locking after finding?

Can we reduce the size of the critical sections by executingfind without locking, and then acquiring the lock only before modifying the list?No, because the list may be modified between when a thread performsfindand when it acquires the lock.

For example, supposethread t runsremove(e)whilethread uruns add(c), andtacquires the lock first:

head µ a µ b µ e µ f µ g µ tail µ pred curr pred curr c 29 / 76

(77)

Locking after finding?

Can we reduce the size of the critical sections by executingfind without locking, and then acquiring the lock only before modifying the list?No, because the list may be modified between when a thread performsfindand when it acquires the lock.

For example, supposethread t runsremove(e)whilethread uruns add(c), andtacquires the lock first:

head a b e f g tail

pred curr

pred curr

c

(78)

Parallel linked sets

Fine-grained locking

(79)

Concurrent set with fine-grained locking

Rather than locking the whole linked list at once, we add alockto each node. Then, threads onlylock the individual nodeson which they are operating.

public class FineSet<T> extends SequentialSet<T>

{

// empty set

public FineSet() {

head = new LockableNode<>(Integer.MIN_VALUE); // smallest key tail = new LockableNode<>(Integer.MAX_VALUE); // largest key head.setNext(tail);

}

// overriding of find, add, remove, and has

(80)

Nodes in a fine-locking set

Each node includes alock object, andlockandunlockmethods that access the lock.

class LockableNode<T> extends SequentialNode<T>

{

private Lock lock = new ReentrantLock();

void lock() { lock.lock(); } // lock node void unlock() { lock.unlock(); } // unlock node }

(81)

How many nodes do we have to lock?

We have seen (inCoarseSet) that we have to lock as soon as we start executingfind. Thus, we start locking the head node andpass the lock alongthe chain of nodes.

How many nodes do we have to hold locked at once? Even though pred’s node is the only node that is actually modified,onlylocking predisnot enough.

For example, ifthread t runsremove(e)whilethread uruns remove(b), it may happen that onlyb’s removal takes place:

head a b e f g tail

pred curr

pred curr

Thus, welock bothpredandcurrat once.

(82)

How many nodes do we have to lock?

We have seen (inCoarseSet) that we have to lock as soon as we start executingfind. Thus, we start locking the head node andpass the lock alongthe chain of nodes.

How many nodes do we have to hold locked at once? Even though pred’s node is the only node that is actually modified,onlylocking predisnot enough.

For example, ifthread t runsremove(e)whilethread uruns remove(b), it may happen that onlyb’s removal takes place:

head a µ b µ e f g tail pred curr pred curr

Thus, welock bothpredandcurrat once.

(83)

How many nodes do we have to lock?

We have seen (inCoarseSet) that we have to lock as soon as we start executingfind. Thus, we start locking the head node andpass the lock alongthe chain of nodes.

How many nodes do we have to hold locked at once? Even though pred’s node is the only node that is actually modified,onlylocking predisnot enough.

For example, ifthread t runsremove(e)whilethread uruns remove(b), it may happen that onlyb’s removal takes place:

head a µ b µ e f g tail pred curr pred curr

Thus, welock bothpredandcurrat once.

(84)

Fine-locking set: method

find

head a b e f g tail

pred currpred currpredpred currcurr

// find while locking pred and curr, return locked position

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration pred = start; curr = start.next(); // from start node

pred.lock(); curr.lock(); // lock pred and curr nodes

while (curr.key < key) {

pred.unlock(); // unlock pred node pred = curr; curr = curr.next(); // move to next node curr.lock(); // lock next node } // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(85)

Fine-locking set: method

find

head a b e f g tail

pred curr

pred currpredpred currcurr

// find while locking pred and curr, return locked position

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration pred = start; curr = start.next(); // from start node

pred.lock(); curr.lock(); // lock pred and curr nodes

while (curr.key < key) {

pred.unlock(); // unlock pred node pred = curr; curr = curr.next(); // move to next node curr.lock(); // lock next node } // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(86)

Fine-locking set: method

find pred curr head µ a µ b e f g tail

pred currpredpred currcurr

// find while locking pred and curr, return locked position

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration pred = start; curr = start.next(); // from start node

pred.lock(); curr.lock(); // lock pred and curr nodes

while (curr.key < key) {

pred.unlock(); // unlock pred node pred = curr; curr = curr.next(); // move to next node curr.lock(); // lock next node } // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(87)

Fine-locking set: method

find pred curr

head a

µ

b e f g tail

pred currpredpred currcurr

// find while locking pred and curr, return locked position

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration pred = start; curr = start.next(); // from start node

pred.lock(); curr.lock(); // lock pred and curr nodes

while (curr.key < key) {

pred.unlock(); // unlock pred node pred = curr; curr = curr.next(); // move to next node curr.lock(); // lock next node } // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(88)

Fine-locking set: method

find pred curr head a µ b e f g tail pred curr pred curr pred curr

// find while locking pred and curr, return locked position

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration pred = start; curr = start.next(); // from start node

pred.lock(); curr.lock(); // lock pred and curr nodes

while (curr.key < key) {

pred.unlock(); // unlock pred node pred = curr; curr = curr.next(); // move to next node curr.lock(); // lock next node } // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(89)

Fine-locking set: method

find pred curr pred curr head a µ b µ e f g tail pred curr pred curr

// find while locking pred and curr, return locked position

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration pred = start; curr = start.next(); // from start node

pred.lock(); curr.lock(); // lock pred and curr nodes

while (curr.key < key) {

pred.unlock(); // unlock pred node pred = curr; curr = curr.next(); // move to next node curr.lock(); // lock next node } // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(90)

Fine-locking set: method

find pred curr pred curr head a b µ e f g tail pred curr pred curr

// find while locking pred and curr, return locked position

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration pred = start; curr = start.next(); // from start node

pred.lock(); curr.lock(); // lock pred and curr nodes

while (curr.key < key) {

pred.unlock(); // unlock pred node pred = curr; curr = curr.next(); // move to next node curr.lock(); // lock next node } // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(91)

Fine-locking set: method

find

pred currpred curr

head a b

µ

e f g tail

pred curr

pred curr

// find while locking pred and curr, return locked position

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration pred = start; curr = start.next(); // from start node

pred.lock(); curr.lock(); // lock pred and curr nodes

while (curr.key < key) {

pred.unlock(); // unlock pred node pred = curr; curr = curr.next(); // move to next node curr.lock(); // lock next node } // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(92)

Fine-locking set: method

find

pred currpred curr

pred curr head a b µ e µ f g tail pred curr

// find while locking pred and curr, return locked position

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration pred = start; curr = start.next(); // from start node

pred.lock(); curr.lock(); // lock pred and curr nodes

while (curr.key < key) {

pred.unlock(); // unlock pred node pred = curr; curr = curr.next(); // move to next node curr.lock(); // lock next node } // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(93)

Fine-locking set: method

find

pred currpred currpred curr

head a b µ e µ f g tail pred curr

// find while locking pred and curr, return locked position

protected Node<T>, Node<T> find(Node<T> start, int key) {

Node<T> pred, curr; // predecessor and current node in iteration pred = start; curr = start.next(); // from start node

pred.lock(); curr.lock(); // lock pred and curr nodes

while (curr.key < key) {

pred.unlock(); // unlock pred node pred = curr; curr = curr.next(); // move to next node curr.lock(); // lock next node } // until curr.key >= key

return (pred, curr); // return position

}

pseudo-code for:new Position<T>(pred, curr)

(94)

Hand-over-hand locking

The lock acquisition protocol used byfindinFineSetis called hand-over-handlocking orlock coupling.

• Always keepingat least one node lockedprevents interference between threads; otherwise this may happen:

head a b e f g tail

pred curr

pred currpred currpred curr

pred curr

• Locking two nodes at once is sufficient topreventproblems with conflicting operations: threads proceed along the linked list in order, without one thread “overtaking” another thread that is further out

• The protocol ensures that locks are acquired by all threads in the same order, thusavoiding deadlocks

(95)

Hand-over-hand locking

The lock acquisition protocol used byfindinFineSetis called hand-over-handlocking orlock coupling.

• Always keepingat least one node lockedprevents interference between threads; otherwise this may happen:

head µ a µ b µ e µ f g tail pred curr pred curr

pred currpred curr

pred curr

• Locking two nodes at once is sufficient topreventproblems with conflicting operations: threads proceed along the linked list in order, without one thread “overtaking” another thread that is further out

• The protocol ensures that locks are acquired by all threads in the same order, thusavoiding deadlocks

(96)

Hand-over-hand locking

The lock acquisition protocol used byfindinFineSetis called hand-over-handlocking orlock coupling.

• Always keepingat least one node lockedprevents interference between threads; otherwise this may happen:

head µ a µ b e f g tail pred curr pred curr

pred currpred curr

pred curr

• Locking two nodes at once is sufficient topreventproblems with conflicting operations: threads proceed along the linked list in order, without one thread “overtaking” another thread that is further out

• The protocol ensures that locks are acquired by all threads in the same order, thusavoiding deadlocks

(97)

Hand-over-hand locking

The lock acquisition protocol used byfindinFineSetis called hand-over-handlocking orlock coupling.

• Always keepingat least one node lockedprevents interference between threads; otherwise this may happen:

head a µ b µ e f g tail pred curr pred curr pred curr pred curr pred curr

• Locking two nodes at once is sufficient topreventproblems with conflicting operations: threads proceed along the linked list in order, without one thread “overtaking” another thread that is further out

• The protocol ensures that locks are acquired by all threads in the same order, thusavoiding deadlocks

(98)

Hand-over-hand locking

The lock acquisition protocol used byfindinFineSetis called hand-over-handlocking orlock coupling.

• Always keepingat least one node lockedprevents interference between threads; otherwise this may happen:

head a b µ e µ f g tail pred curr

pred currpred curr

pred curr

pred curr

• Locking two nodes at once is sufficient topreventproblems with conflicting operations: threads proceed along the linked list in order, without one thread “overtaking” another thread that is further out

• The protocol ensures that locks are acquired by all threads in the same order, thusavoiding deadlocks

(99)

Hand-over-hand locking

The lock acquisition protocol used byfindinFineSetis called hand-over-handlocking orlock coupling.

• Always keepingat least one node lockedprevents interference between threads; otherwise this may happen:

head a b µ e µ f g tail pred curr

pred currpred curr

pred curr

pred curr

• Locking two nodes at once is sufficient topreventproblems with conflicting operations: threads proceed along the linked list in order, without one thread “overtaking” another thread that is further out

• The protocol ensures that locks are acquired by all threads in the same order, thusavoiding deadlocks

(100)

Hand-over-hand locking

The lock acquisition protocol used byfindinFineSetis called hand-over-handlocking orlock coupling.

• Always keepingat least one node lockedprevents interference between threads; otherwise this may happen:

head a b e µ f µ g tail pred curr

pred currpred currpred curr

pred curr

• Locking two nodes at once is sufficient topreventproblems with conflicting operations: threads proceed along the linked list in order, without one thread “overtaking” another thread that is further out

• The protocol ensures that locks are acquired by all threads in the same order, thusavoiding deadlocks

(101)

Hand-over-hand locking

The lock acquisition protocol used byfindinFineSetis called hand-over-handlocking orlock coupling.

• Always keepingat least one node lockedprevents interference between threads; otherwise this may happen:

head a b e µ f µ g tail pred curr

pred currpred currpred curr

pred curr This node has been removed!

• Locking two nodes at once is sufficient topreventproblems with conflicting operations: threads proceed along the linked list in order, without one thread “overtaking” another thread that is further out

• The protocol ensures that locks are acquired by all threads in the same order, thusavoiding deadlocks

(102)

Hand-over-hand locking

The lock acquisition protocol used byfindinFineSetis called hand-over-handlocking orlock coupling.

• Always keepingat least one node lockedprevents interference between threads; otherwise this may happen:

head a b e µ f µ g tail pred curr

pred currpred currpred curr

pred curr This node has been removed!

• Locking two nodes at once is sufficient topreventproblems with conflicting operations: threads proceed along the linked list in order, without one thread “overtaking” another thread that is further out

• The protocol ensures that locks are acquired by all threads in the same order, thusavoiding deadlocks

(103)

Fine-locking set: method

add

head a b e f g tail

pred curr

node: c

public boolean add(T item) {

Node<T> node = new LockableNode<>(item); // new node

try { // find with hand-over-hand locking

// the first position such that curr.key() >= item.key() Node<T> pred, curr = find(head, item.key()); // locking ... // add node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(104)

Fine-locking set: method

add head a b µ e µ f g tail pred curr node: c

public boolean add(T item) {

Node<T> node = new LockableNode<>(item); // new node

try { // find with hand-over-hand locking

// the first position such that curr.key() >= item.key() Node<T> pred, curr = find(head, item.key()); // locking ... // add node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(105)

Fine-locking set: method

add head a b µ e µ f g tail pred curr node: c

public boolean add(T item) {

Node<T> node = new LockableNode<>(item); // new node

try { // find with hand-over-hand locking

// the first position such that curr.key() >= item.key() Node<T> pred, curr = find(head, item.key()); // locking ... // add node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(106)

Fine-locking set: method

add

head a b e f g tail

pred curr

node: c

public boolean add(T item) {

Node<T> node = new LockableNode<>(item); // new node

try { // find with hand-over-hand locking

// the first position such that curr.key() >= item.key() Node<T> pred, curr = find(head, item.key()); // locking ... // add node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(107)

Fine-locking set: method

add

head a b e f g tail

pred curr

node: cc

public boolean add(T item) {

Node<T> node = new LockableNode<>(item); // new node

try { // find with hand-over-hand locking

// the first position such that curr.key() >= item.key() Node<T> pred, curr = find(head, item.key()); // locking ... // add node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(108)

Fine-locking set: method

remove

head a b e f g tail

pred curr

public boolean remove(T item) {

try { // find with hand-over-hand locking

// the first position such that curr.key >= item.key Node<T> pred, curr = find(head, item.key()); // locking ... // remove node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(109)

Fine-locking set: method

remove head a b µ e µ f g tail pred curr

public boolean remove(T item) {

try { // find with hand-over-hand locking

// the first position such that curr.key >= item.key Node<T> pred, curr = find(head, item.key()); // locking ... // remove node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(110)

Fine-locking set: method

remove head a b µ e µ f g tail head a b µ e f g tail pred curr

public boolean remove(T item) {

try { // find with hand-over-hand locking

// the first position such that curr.key >= item.key Node<T> pred, curr = find(head, item.key()); // locking ... // remove node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(111)

Fine-locking set: method

remove

head a b

µ

f g tail

pred curr

public boolean remove(T item) {

try { // find with hand-over-hand locking

// the first position such that curr.key >= item.key Node<T> pred, curr = find(head, item.key()); // locking ... // remove node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(112)

Fine-locking set: method

remove

head a b f g tail

pred curr

public boolean remove(T item) {

try { // find with hand-over-hand locking

// the first position such that curr.key >= item.key Node<T> pred, curr = find(head, item.key()); // locking ... // remove node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(113)

Fine-locking set: method

has

head a b e f g tail

pred curr pred curr

public boolean has(T item) {

try { // find with hand-over-hand locking

// the first position such that curr.key() >= item.key() Node<T> pred, curr = find(head, item.key()); // locking ... // check node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(114)

Fine-locking set: method

has head a b µ e µ f g tail pred curr pred curr

public boolean has(T item) {

try { // find with hand-over-hand locking

// the first position such that curr.key() >= item.key() Node<T> pred, curr = find(head, item.key()); // locking ... // check node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(115)

Fine-locking set: method

has head a b µ e µ f g tail pred curr pred curr

public boolean has(T item) {

try { // find with hand-over-hand locking

// the first position such that curr.key() >= item.key() Node<T> pred, curr = find(head, item.key()); // locking ... // check node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(116)

Fine-locking set: method

has

head a b e f g tail

pred curr

pred curr

public boolean has(T item) {

try { // find with hand-over-hand locking

// the first position such that curr.key() >= item.key() Node<T> pred, curr = find(head, item.key()); // locking ... // check node as in SequentialSet, while locking

} finally { pred.unlock(); curr.unlock(); } // done: unlocking

}

(117)

Fine-locking set: pros and cons

Pros:

• if locks are fair, so is access to the set, because threads proceed along the list one after the other without changing order

• threads operating on disjoint portions of the list may be able to operate in parallel

Cons:

• it is still possible that one thread prevents another thread from operating in parallel on a disjoint portion of the list – for example, if one thread wants to access the end of the list but another thread blocks it while locking the beginning of the list • the hand-over-hand locking protocol may be quite slow, as it

involves a significant number of lock operations

(118)

Parallel linked sets

Optimistic locking

(119)

Concurrent set with optimistic locking

Let us revisit the idea of performingfindwithout locking. We have seen that problems may occur if the list is modified between when a threads finds a position and when it acquires locks on that position. Thus, wevalidatea positionafter finding itand while the nodes are locked, to verify that no interference took place.

public class OptimisticSet<T> extends SequentialSet<T>

{

public FineSet()

{ head = new ReadWriteNode<>(Integer.MIN_VALUE); // smallest key tail = new ReadWriteNode<>(Integer.MAX_VALUE); // largest key head.setNext(tail); }

// is (pred, curr) a valid position?

protected boolean valid(Node<T> pred, Node<T> curr) // ...

// overriding of find, add, remove, and has

(120)

Nodes in an optimistic-locking set

Since we need to be able tofollowthe chain ofnextreferences without locking, attributenextmust be declaredvolatilein Java – so that modifications to it (which occur while the node is locked) are propagated to all threads (even if they have not locked a node). Other than for this detail, aReadWriteNodeis the same as aLockableNode. With a little abuse of notation, we can pretend thatReadWriteNode inherits fromLockableNodeand overrides itsnextattribute. Overriding of attributes is however not possible in Java (shadowing takes place instead); the actual implementation that we make available does not reuseLockableNode’s code through inheritance.

class ReadWriteNode<T> extends LockableNode<T>

{

private volatile Node<T> next; // next node in chain

}

(121)

Delayed locking as optimistic locking

InOptimisticSet, operations work as follows:

1. findthe item’s position inside the list without locking – as in SequentialSet

2. lockthe position’s nodespredandcurr

3. validatethe position while the nodes are locked:

3.1 if the position is valid,perform the operationwhile the nodes are locked, then release locks

3.2 if the position is invalid, release locks andrepeat the operationfrom scratch

This approach isoptimisticbecause it works well when validation is often successful (so we don’t have to repeat operations).

(122)

Delayed locking as optimistic locking

InOptimisticSet, operations work as follows:

1. findthe item’s position inside the list without locking – as in SequentialSet

2. lockthe position’s nodespredandcurr

3. validatethe position while the nodes are locked:

3.1 if the position is valid,perform the operationwhile the nodes are locked, then release locks

3.2 if the position is invalid, release locks andrepeat the operationfrom scratch

This approach isoptimisticbecause it works well when validation is often successful (so we don’t have to repeat operations).

References

Related documents

Our hearts went out to these women, and naturally we wanted to help if we could, but the idea of helping hormonal infertility seemed far-fetched.. While we had good evidence that

Abstract; In the southern border provinces of Thailand, especially Pattani, Yala and Narathiwat, there are two religious group of people, Thai Muslims and

Dietetic Internship: Oncology, Nutrition Support, Diet and Cancer Prevention, Food Allergies Instructor , School of Allied Health, University of Nebraska Medical Center, Omaha,

The project development objectives (PDOs) were &#34; to improve the capacity of the main navigation channel in Meizhou Bay and enhance the management capacity of Meizhou Bay

Once data processing had been completed, analyses using bootstrapping mediation procedures were used to determine whether personal stigma, attitudes, and barriers

Lötsch: Drug therapy against pain: pharmacokinetics, pharmacogenetics, and drug development.. Winter Semester 2011

Plan Steps Responsible Parties Target Date/Timeline Resources ($, people) Indicators of Success /Notes. Progress Report (date)_________ A1- Provide training IT staff /

• Cancer care is complex and new oral therapies continue to be developed and marketed. • Reimbursement will continue to be