Low-level concurrency (reinvent vehicle)

48
Low level concurrency Reinventing vehicle Olena Syrota

Transcript of Low-level concurrency (reinvent vehicle)

Low level concurrency

Reinventing vehicle

Olena Syrota

Motivation• Where is computer science in software

engineering?• Not technologies• Real computer science• Algorithms• Algorithm’s properties

Literature

Low level concurrencyMutex

Mutual Exclusion (mutex)• Q: How to implement mutex in software?• A: Using some “lock” library

Mutex - back to computer science

• Let’s imagine you have no Lock mechanism• You have to invent it!!!

Reinventing first wheel

Scene

• Alice and Bob are neighbors• They share a yard• Alice has a cat• Bob has a dog

• Shared yard – is shared resource

We have to invent protocol for mutual exclusive access to resource (mutual exclusion)

Protocol 0 • Alice and Bob never release the pets

Properties• Mutual exclusion. Resource is not used at the same

time

Protocol 1 - asking

• Shouting, Knocking on the door, Cell phone

• Protocol: • Ask if I can release the pet

• Good• Comparing with protocol 0 pets can

reach the yard

• Bad:• If Alice is knocking to the Bob’s door

(shouting, dialing) and• Bob is not at home or does not hear,

turned off phone etc

Two problems

• Message can be lost• if other side is

unreachable at the moment it will never know about our intention

• Protocol requires interaction with other side. • Other side can be

unreachable. • When translating to

computer “language” we will spend time of other side to solve task. This is overhead.

Properties - waiting• Waiting• Can we reduce time of waiting, and wait only when

resource is busy but not when counterpart is not available?

• We need to persist intention to lock shared resource to reduce waiting

Persistent communication• Mutual exclusion requires persistent

communication

Protocol 2 - cans

Bob Alice

Yard

Protocol 2 - cans

Cans (one or several) are on the Bob’s windowsill

• The protocol is:• Alice

• Alice yanks the string to knock over of the cans

• When Bob notices that can is knocked over, he resets the can

• Alice release the cat

• Bob• Bob release the dog

whenever he needs it

• Good:• Alice’s intension is “persistent”

• Bad:• Bob can be out of home• Bob can notice that can is

knocked over too late• Not-fair protocol

Problem

This protocol divert counterpart and we are still waiting

17

Bob Alice

Yard

Protocol 3 – flags

Protocol 3 – flagsBob

1. He raises his flag.2. Look at Alice’s flag. If

is lowered, he unleashes his dog.

3. When his dog comes back, he lowers his flag.

Alice

1. She raises her flag.2. Look at Bob’s flag. If is

lowered, she unleashes her cat.

3. When her cat comes back, she lowers her flag.

What is wrong? If Alice and Bob will do steps simultaneously the pets will never reach the yard. Livelock.

Propeties - livelock• Livelock. Livelocked threads are unable to make

further progress. However, the threads are not blocked — they are simply too busy responding to each other to resume work.

Protocol 3 – flags. With priority.Bob (gives way to Alice to avoid live-lock)

1. He raises his flag.2. While Alice’s flag is raised

1. Bob lowers his flag2. Bob waits until Alice’s flag is

lowered3. Bob raises his flag

3. As soon as his flag is raised and hers is down, he unleashes his dog.

4. When his dog comes back, he lowers his flag.

Alice

1. She raises her flag.2. When Bob’s flag is

lowered, she unleashes her cat.

3. When her cat comes back, she lowers her flag.

This is not fair algorithm since it give priority to Alice. Bob’s dog can

wait forever with this approach. Starvation.

What is wrong?

Code itboolean flag[];flag[0] = false; flag[1] = false;

Bob:flag[1] = true;if (flag[0] == true) { flag[1] = false; while (flag[0] == true) { } flag[1] = true;}// critical section...flag[1] = false;// remainder section

Alice:flag[0] = true;while (flag[1] == true) {}

// critical section...flag[0] = false;// remainder section

Properties - starvation• Starvation - a thread is unable to gain regular

access to shared resources and is unable to make progress. This happens when shared resources are made unavailable for long periods by "greedy" threads

Dekker’s algorithm• Last slides we presented non-fair algorithm• Fair variant is called Dekker’s algorithm• priority is turned every time when lock needed

• Authors of Dekker’s algorithm: Dekker, Dijkstra

• It allows two threads to share a single-use resource without conflict, using only shared memory for communication

Dekker’s algorithms(var “turn” introduced to change priority)boolean flag[];flag[0] = false; flag[1] = false; int turn = 0 // or 1

P0:flag[0] = true;while (flag[1] == true) { if (turn ≠ 0) { flag[0] = false; while (turn ≠ 0) { // busy wait } flag[0] = true; }}// critical section...turn = 1;flag[0] = false;// remainder section

P1:flag[1] = true;while (flag[0] == true) { if (turn ≠ 1) { flag[1] = false; while (turn ≠ 1) { // busy wait } flag[1] = true; }}// critical section...turn = 0;flag[1] = false;// remainder section

Properties of good lock-protocol• Mutual Exclusion

• Resource is not used at the same time

• Deadlock freedom• if one pet wants to enter the yard, then it eventually succeeds • if both pets want to enter the yard, then eventually at least one of them

succeeds

• Starvation freedom• if a pet wants to enter the yard, will it eventually succeed?• If resource was locked, will it be locked out?

• Livelock-freedom• livelock is a special case of starvation. This property is proved via resource

starvation

• Waiting• Imagine that Alice raises her flag, and is then suddenly stricken with

appendicitis. The protocol states that Bob (and his dog) must wait for Alice to lower her flag. If Alice is delayed, then Bob is also delayed

Deadlock• For API users deadlock is:• Deadlock describes a situation where two or more threads are

blocked forever, waiting for each other.• Deadlock is easy to understand with 2 threads and 2+ shared

resources

• But we are protocol developers (not API users). We need deadlock-freedom property for lock

• The API developers meaning of deadlock is: If some thread attempts to acquire the lock, then some thread will succeed in acquiring the lock. If thread A calls lock() but never acquires the lock, then other threads must be completing an infinite number of critical sections

Summary

Property/Protocol “Flags” With priority

Dekker’s

Mutual Exclusion + +

Deadlock freedom + +

Starvation freedom - +

Starvation free -> deadlock free

• Mutual exclusion between two threads can be solved (however imperfectly) using only two one-bit variables, each of which can be written by one thread and read by the other.

Concurrent prime-numbers

// shared by all threads1 Counter counter = new Counter(1);

// next function is executed in parallel2 void primePrint() { 3 long i = 0;4 long limit = power(10, 10);5 while (i < limit) {6 i = counter.getAndIncrement(); 7 if (isPrime(i))8 print(i);9 }10 }

class Counter {

private int cntr;

private Lock mutex;

public int getAndIncrement() {

mutex.lock();

try {

cntr++; // body

} finally {

mutex.unlock();

}

}

}

Other algorithms for reinventing vehicles

2 threads cases

LockOne algorithm• Only for 2 threads• Thread Ids: 0, 1• i = ThreadID.get()• Other thread’s id j=1-i

LockOne algorithm (2-Thread Solution)

1 class LockOne implements Lock { // thread-local index, 0 or 12 private boolean[] flag = new boolean[2];

4 public void lock() {5 int i = ThreadID.get();6 int j = 1 - i;7 flag[i] = true;8 while (flag[j]) {} // wait9 }

10 public void unlock() {11 int i = ThreadID.get();12 flag[i] = false;13 }14 }

Lock and unlock functions can be

called concurrently and race condition

can occur

LockOne algorithm (2-Thread Solution)

1 class LockOne implements Lock { // thread-local index, 0 or 12 private boolean[] flag = new boolean[2];

4 public void lock() {5 int i = ThreadID.get();6 int j = 1 - i;7 flag[i] = true;8 while (flag[j]) {} // wait9 }

10 public void unlock() {11 int i = ThreadID.get();12 flag[i] = false;13 }14 }

Thread 0

Two threads call lock/unlocksimultaneously

Thread 1

Properties of LockOne

• Mutual exclusion• Deadlock free

• If writeA(flag[A] = true) and writeB(flag[B] = true) events occur before readA(flag[B]) and readB(flag[A]) events, then both threads wait forever.

• LockOne has an interesting property: if one thread runs before the other, no deadlock occurs, and all is well.

1 class LockOne implements Lock {

// thread-local index, 0 or 1

2 private boolean[] flag = new boolean[2];

4 public void lock() {

5 int i = ThreadID.get();

6 int j = 1 - i;

7 flag[i] = true;

8 while (flag[j]) {} // wait

9 }

10 public void unlock() {

11 int i = ThreadID.get();

12 flag[i] = false;

13 }

14 }

LockTwo1 class LockTwo implements Lock {

2 private volatile int victim;

3 public void lock() {

4 int i = ThreadID.get();

5 victim = i; // let the other go first

6 while (victim == i) {} // wait

7 }

8 public void unlock() {}

9 }

LockTwo

1 class LockTwo implements Lock {

2 private volatile int victim;

3 public void lock() {

4 int i = ThreadID.get();

5 victim = i; // let the other go first

6 while (victim == i) {} // wait

7 }

8 public void unlock() {}

9 }

Thread 0

One thread runs completely before other

Thread 1

LockTwo

1 class LockTwo implements Lock {

2 private volatile int victim;

3 public void lock() {

4 int i = ThreadID.get();

5 victim = i; // let the other go first

6 while (victim == i) {} // wait

7 }

8 public void unlock() {}

9 }

• Mutual exclusion• Deadlock free

• it deadlocks if one thread runs completely before the other

• LockTwo has an interesting property: if the threads run concurrently, the lock() method succeeds

Peterson Lock1 class Peterson implements Lock {

2 // thread-local index, 0 or 1

3 private volatile boolean[] flag = new boolean[2];

4 private volatile int victim;

5 public void lock() {

6 int i = ThreadID.get();

7 int j = 1 - i;

8 flag[i] = true; // I’m interested

9 victim = i; // you go first

10 while (flag[j] && victim == i) {}; // wait

11 }

12 public void unlock() {

13 int i = ThreadID.get();

14 flag[i] = false; // I’m not interested

15 }

16 }

• When implementing in Java use volatile and AtomicIntegerArray (java.util.concurrent.atomic)

Properties of Peterson algorithm• Mutual exclusion• Starvation free• Deadlock free

N threads cases

Lamport’s Bakery Algorithm• first-come-first-served

• When a thread wants to enter its critical section, it sets a flag, and takes a number greater than the numbers of all other threads.

• When all lower numbers have been served, the thread can enter.

• At leaving its critical section, the thread resets its flag.

Lamport’s Bakery Algorithm1 class Bakery implements Lock {2 boolean[] flag;3 Label[] label;4 public Bakery (int n) {5 flag = new boolean[n];6 label = new Label[n];7 for (int i = 0; i < n; i++) {8 flag[i] = false; label[i] = 0;9 }10 }11 public void lock() {12 int i = ThreadID.get();13 flag[i] = true;14 label[i] = max(label[0], ...,label[n-1]) + 1;15 while (( k != i)(flag[k] && (label[k],k) << (label[i],i))) {};16 }17 public void unlock() {18 flag[ThreadID.get()] = false;19 }20 }

flag[A] - whether A wants toenter the critical sectionlabel[A] - indicates the thread’srelative order when entering the bakery<< lexicographical order

(label[i], i) << (label[j], j)if and only if

label[i] < label[j] or label[i] = label[j] and i < j

Properties of Bakery Algorithm• Mutual exclusion• Starvation free• Deadlock free• First-come-first-served

• Note that any algorithm that is both deadlock-free and first-come-first-served is also starvation-free.

We considered• We considered• Dekker’s• Peterson• Lamport’s Bakery

• We did not considered compareAndSet approach, atomics, STM, actors

Homework• Select lock library (select your favorite lock

algorithm)• Remember of reordering. Use volatile or Atomic for

shared variable.

• Implement use case (concurrent stack, concurrent prime-numbers, etc)