Mutual Exclusion: by Shiran Mizrahi
Mutual Exclusion: by Shiran Mizrahi
By Shiran Mizrahi
Critical Section
class Counter { private int value = 1; public Counter(int c) { value = c; } public int inc() { int temp = value; value = temp+1; return temp; } //counter starts at one //constructor initializes counter
//increment value & return prior value //start of danger zone //end of danger zone
Critical Section
The problem occurs if two threads both read the value field at the line marked start of danger zone, and then both update that field at the line marked end of danger zone.
Critical Section
int temp = value; value = temp+1;
Value read 1
2
read 2 write 2
write 3
read 1
write 2
time
entry code
critical section
The problem is to design the entry and exit code in a way that guarantees that the mutual exclusion and deadlock-freedom properties are satisfied.
exit code
Good properties
Mutual Exclusion: No two threads are in their critical sections at the same time.
Deadlock-freedom: If a thread is trying to enter its critical section, then some thread, not necessarily the same one, eventually enters its critical section. Starvation-freedom: If a thread is trying to enter its critical section, then this thread must eventually enter its critical section. Starvation-freedom is a stronger property than Deadlock-freedom.
Discussion Topics
The mutual exclusion problem and proposed algorithms Petersons algorithm Kessels single-writer algorithm Tournament algorithms The Filter algorithm The Bakery algorithm
Some notations
AB event A precedes event B CSA thread A is in the critical section writeA(x=v) the event in which thread A writes to x readA(x==v) the event in which thread A reads from x
Algorithm 1
Thread 0 flag[0] = true while (flag[1]) {} critical section flag[0]=false Thread 1 flag[1] = true while (flag[0]) {} critical section flag[1]=false
Mutual Exclusion
Proof
Assume in the contrary that two threads can be in their critical section at the same time. From the code we can see: write0(flag[0]=true) read0(flag[1]==false) CS0 write1(flag[1]=true) read1(flag[0]==false) CS1
Proof
We get: write0(flag[0]=true) read0(flag[1]==false) write1(flag[1]=true) read1(flag[0]==false) That means that thread 0 writes (flag[0]=true) and then thread 1 reads that (flag[0]==false), a contradiction.
Deadlock freedom
Algorithm 1 fails dead-lock freedom: Concurrent execution can deadlock. If both threads write flag[0]=true and flag[1]=true before reading (flag[0]) and (flag[1]) then both threads wait forever.
Algorithm 2
Thread 0 victim = 0; while (victim == 0) {}; critical section Thread 1 victim = 1; while (victim == 1) {}; critical section
Mutual Exclusion
Proof
Assume in the contrary that two threads can be in their critical section at the same time. From the code we can see: write0(victim=0) read0(victim==1) CS0 write1(victim=1) read1(victim==0) CS1
Proof
Since thread 1 must assign 1 to victim between the events write0(victim=0) and read0(victim==1), and since this assignment is the last, we get: write0(victim=0) write1(victim=1) read0(victim==1) Once victim is set to 1, it does not change, so every read will return 1, and this is a contradiction to the former equation: write1(victim=1) read1(victim==0) CS1
Deadlock freedom
Algorithm 2 also fails deadlock freedom. It deadlocks if one thread runs completely before the other.
Well describe two algorithms that solve the mutual exclusion problem for two Threads. They are also deadlock-free and starvation free.
Petersons Algorithm
Thread 0 flag[0] = true victim = 0 while (flag[1] and victim == 0) {skip} critical section flag[0] = false Thread 1 flag[1] = true victim = 1 while (flag[0] and victim == 1) {skip} critical section flag[1] = false
Petersons Algorithm
0/1 indicates that the thread is contending for the critical section by setting flag[0]/flag[1] to true. victim shows who got last Then if the value of flag[i] is true then there is no contending by other thread and the thread can start executing the critical section. Otherwise the first who writes to victim is also the first to get into the critical section
Thread 0 flag[0] = true victim = 0 while (flag[1] and victim == 0) {skip} critical section flag[0] = false Thread 1 flag[1] = true victim = 1 while (flag[0] and victim == 1) {skip} critical section flag[1] = false
victim := i
Barrier
no / maybe
The structure shows that the first thread to cross the barrier is the one which gets to enter the critical section. When there is no contention a thread can enter the critical section immediately.
First to cross the barrier? victim = j ?
flag[j] = true ?
Contention?
yes
Mutual Exclusion
Proof
Assume in the contrary that two threads can be in their critical section at the same time. From the code we see: (*) write0(flag[0]=true) write0(victim=0) read0(flag[1]) read0(victim) CS0 write1(flag[1]=true) write1(victim=1) read1(flag[0]) Thread 0 Thread 1 read1(victim) CS1
flag[0] = true victim = 0 while (flag[1] and victim == 0) {skip} critical section flag[0] = false flag[1] = true victim = 1 while (flag[0] and victim == 1) {skip} critical section flag[1] = false
Proof
Assume that the last thread to write to victim was 0. Then: write1(victim=1) write0(victim=0) This implies that thread 0 read that victim=0 in equation (*) Since thread 0 is in the critical section, it must have read flag[1] as false, so: write0(victim=0) read0(flag[1]==false)
Thread 0 flag[0] = true victim = 0 while (flag[1] and victim == 0) {skip} critical section flag[0] = false Thread 1 flag[1] = true victim = 1 while (flag[0] and victim == 1) {skip} critical section flag[1] = false
Proof
Then, we get:
write1(flag[1]=true) write1(victim=1) write0(victim=0) read0(flag[1]==false)
Thus:
write1(flag[1]=true) read0(flag[1]==false)
There was no other write to flag[1] before the critical section execution and this yields a contradiction.
Thread 0 flag[0] = true victim = 0 while (flag[1] and victim == 0) {skip} critical section flag[0] = false Thread 1 flag[1] = true victim = 1 while (flag[0] and victim == 1) {skip} critical section flag[1] = false
Starvation freedom
Proof
Assume to the contrary that the algorithm is not starvation-free Then one of the threads, say thread 0, is forced to remain in its entry code forever
Thread 0 flag[0] = true victim = 0 while (flag[1] and victim == 0) {skip} critical section flag[0] = false Thread 1 flag[1] = true victim = 1 while (flag[0] and victim == 1) {skip} critical section flag[1] = false
Proof
This implies that at some later point thread 1 will do one of the following three things: 1. Stay in its remainder forever 2. Stay in its entry code forever, not succeeding and proceeding into its critical section 3. Repeatedly enter and exit its critical section
Well show that each of the three possible cases leads to a contradiction.
Thread 0 flag[0] = true victim = 0 while (flag[1] and victim == 0) {skip} critical section flag[0] = false Thread 1 flag[1] = true victim = 1 while (flag[0] and victim == 1) {skip} critical section flag[1] = false
Proof
In the first case flag[1] is false, and hence thread 0 can proceed. The second case is impossible since victim is either 0 or 1, and hence it always enables at least one of the threads to proceed. In the third case, when thread 1 exit its critical section and tries to enter its critical section again, it will set victim to 1 and will never change it back to 0, enabling thread 0 to proceed.
Thread 0 flag[0] = true victim = 0 while (flag[1] and victim == 0) {skip} critical section flag[0] = false Thread 1 flag[1] = true victim = 1 while (flag[0] and victim == 1) {skip} critical section flag[1] = false
Thread 0 can write the registers victim[0] and flag[0] and read the registers victim[1] and flag[1] Thread 1 can write the registers victim[1] and flag[1] and read the registers victim[0] and flag[0]
How can we use a two-thread algorithm to construct an algorithm for many threads?
Tournament Algorithms
Tournament Algorithms
A simple method which enables the construction an algorithm for n threads from any given algorithm for two threads. Each thread is progressing from the leaf to the root, where at each level of the tree it participates in a two thread mutual exclusion algorithm. As a thread advanced towards the root, it plays the role of thread 0 when it arrives from the left subtree, or of thread 1 when it arrives from the right subtree.
A direct generalization of Petersons algorithm to multiple threads. The Peterson algorithm used a two-element boolean flag array to indicate whether a thread is interested in entering the critical section. The filter algorithm generalizes this idea with an N-element integer level array, where the value of level[i] indicates the latest level that thread i is interested in entering.
ncs
level n-1
cs
Filter
level 0
ncs
level n-1
cs
Filter
Thread i
for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i level[k] >= L) and victim[L] == i ) {} } critical section level[i] = 0;
Filter
Thread i for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i level[k] >= L) and victim[L] == i ) {} } critical section level[i] = 0;
Filter
Thread i for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i level[k] >= L) and victim[L] == i ) {} } critical section level[i] = 0;
Give priority to anyone but me (at every level)
Filter
Thread i for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i level[k] >= L) and victim[L] == i ) {} } critical section level[i] = 0;
Wait as long as someone else is at same or higher level, and Im designated victim. Thread enters level L when it completes the loop.
Claim
There are at most n-L threads enter level L Proof: by induction on L and by contradiction At L=0 trivial Assume that there are at most n-L+1 threads at level L-1. Assume that there are n-L+1 threads at level L Let A be the last thread to write victim[L] and B any other thread at level L
Proof structure
ncs
cs
Show that A must have seen B at level L and since victim[L] == A could not have entered
Proof
writeB(level[B]=L)writeB(victim[L]=B)
writeA(victim[L]=A)readA(level[B])
writeB(victim[L]=B)writeA(victim[L]=A)
for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i level[k] >= L) and victim[L] == i ) {} } critical section level[i] = 0;
Proof
Since B is at level L, when A reads level[B], it reads a value greater than or equal L and so A couldnt completed its loop and still waiting (remember that victim=A), a contradiction.
for (int L = 1; L < n; L++) { level[i] = L; victim[L] = i; while (($ k != i level[k] >= L) and victim[L] == i ) {} } critical section level[i] = 0;
A conclusion
The filter algorithm satisfies mutual exclusion At level n-1 there are at most n-(n-1)=1 threads, which means at most one thread in the critical section
Starvation-freedom
Fairness
Starvation freedom guarantees that if a thread is trying to enter its critical section, it will eventually do so There is no guarantee about how long it will take We wish for fairness: if thread A enters the entry code before thread B, then A should enter the critical section first
Bounded waiting
Doorway interval:
- Written DA - always finishes in finite steps doorway
waiting
remainder
entry code
Waiting interval:
- Written WA - may take unbounded steps
critical section
exit code
Mutual Exclusion
Deadlock-freedom
Starvation-freedom FIFO
r-Bounded Waiting
If DAk DB j
As k-th doorway precedes Bs j-th doorway As k-th critical section precedes Bs (j+r)-th critical section B cannot overtake A by more than r times
First-come-first-served means r = 0.
No one starves But very weak fairness Not r-bounded for any r! Thats pretty lame
Bakery Algorithm
The idea is similar to a line at the bakery A customer takes a number greater than numbers of other customers Each of the threads gets a unique identifier
Bakery Algorithm
Thread i flag[i]=true; number[i] = max(number[0], ,number[n-1])+1; while ($ k!= i flag[k] && (number[i],i) > (number[k],k)) {}; critical section flag[i] = false;
Bakery Algorithm
Doorway
flag[i]=true; number[i] = max(number[0], ,number[n-1])+1; while ($ k!= i flag[k] && (number[i],i) > (number[k],k)) {}; critical section flag[i] = false;
Bakery Algorithm
flag[i]=true; number[i] = max(number[0], ,number[n-1])+1; while ($ k!= i flag[k] && (number[i],i) > (number[k],k)) {}; critical section flag[i] = false;
Im interested
Bakery Algorithm
flag[i]=true; number[i] = max(number[0], ,number[n-1])+1; while ($ k!= i flag[k] && (number[i],i) > (number[k],k)) {}; critical section flag[i] = false;
Bakery Algorithm
flag[i]=true; number[i] = max(number[0], ,number[n-1])+1; while ($ k!= i flag[k] && (number[i],i) > (number[k],k)) {}; critical section flag[i] = false;
Someone is interested
Bakery Algorithm
flag[i]=true; number[i] = max(number[0], ,number[n-1])+1; while ($ k!= i flag[k] && (number[i],i) > (number[k],k)) {}; critical section flag[i] = false;
There is someone with a lower number and identifier. pair (a,b) > (c,d) if a>c, or a=c and b>d (lexicographic order)
Deadlock freedom
The bakery algorithm is deadlock free Some waiting thread A has a unique least (number[A],A) pair, and that thread can enter the critical section
FIFO
Starvation freedom
The bakery algorithm satisfies deadlock freedom and first-come-first-served and those properties implies starvation freedom
Mutual Exclusion
Suppose A and B in CS together Suppose A has an earlier number When B entered, it must have seen
flag[i]=true; number[i] = max(number[0], ,number[n-1])+1; while ($ k!= i flag[k] && (number[i],i) > (number[k],k)) {}; critical section flag[i] = false;
Mutual Exclusion
numbers are strictly increasing so B must have seen (flag[A] == false) numberingB readB(flag[A]) writeA(flag[A]) numberingA Which contradicts the assumption that A has an earlier number
flag[i]=true; number[i] = max(number[0], ,number[n-1])+1; while ($ k!= i flag[k] && (number[i],i) > (number[k],k)) {}; critical section flag[i] = false;