Lecture 5

Mutual Exclusion

Events

Concurrency


Where yellow shows events from thread AA and green shows events from thread BB

Intervals

Precedence Ordering

Repeated events

while (mumble){
    a_0 ; a_1
}

Here we have to distinguish between each run of the same events
i.e. a0ka_0^k representing the kthk^{th} occurrence of event a0a_0 and A0kA_0^k is the kthk^{th} occurrence of interval A0=(a0,a1)A_0 = (a_0,a_1)

We make variables indivisible using locks, i.e. able to withstand attempted concurrent accesses.

Locks

In Java, we can use the Lock interface

public interface Lock {

    public void lock();

    public void unlock();
}
public class Counter {
    private long value;
    private Lock lock;
    public long getAndIncrement() {
        lock.lock();
        try{
            value ++;
        }finally{
            lock.unlock();
        }
        return temp;
    }
}

Desired Properties of Locks

Mutual Exclusion:

Deadlock Free:

Starvation Free:

LockOne, for 2 threads

Note: ii is the current thread and jj is the other thread

class LockOne implements Lock {
    private boolean[] flag = new boolean[2]
    public void lock(){
        int i = ThreadID.get();
        int j = 1-i;
        flag[i] = true;
        while (flag[j]) {/*wait*/}
    }
    public void unlock(){
        int i = ThreadID.get();
        flag[i] = false;
    }
}
Proof LockOne Satisfies Mutual Exclusion

From the code:

writeA(flag[A]=true)[6]readA(flag[B]==false)[7]CSAwrite_A(flag[A]=true)^{[6]} \prec read_A(flag[B]==false)^{[7]} \prec CS_A

writeB(flag[B]=true)[2]readB(flag[A]==false)[3]CSBwrite_B(flag[B]=true)^{[2]} \prec read_B(flag[A]==false)^{[3]} \prec CS_B

From our assumption:

readA(flag[B]==false)[8]writeB(flag[B]=true)[1]read_A(flag[B]==false)^{[8]} \prec write_B(flag[B]=true)^{[1]}

readB(flag[A]==false)[4]writeA(flag[A]=true)[5]read_B(flag[A]==false)^{[4]} \prec write_A(flag[A]=true)^{[5]}

We can then see a cycle form between [1],[2],[3],[4],[5],[6],[7]and[8][1],[2],[3],[4],[5], [6], [7] \text{and} [8], therefore such a series of events is impossible due to it being a partial order.

Proof LockOne Satisfies Deadlock Freedom
flag[i] = true;
while(flag[j]){}
flag[j] = true;
while(flag[i]){}

LockTwo for 2 threads

public class LockTwo implements Lock { 
    private int victim; 
    public void lock() {
        victim = i;
        while (victim == i){/* wait */}
    }
    public void unlock(){}
}

Peterson's Algorithm

public void lock(){
    flag[i] = true;
    victim = i;
    while (flag[j] && victim == i){/* wait */}
}
public void unlock(){
    flag[i] = false;
}
Mutual Exclusion for Peterson's Algorithm

From the code:

(1)

writeB(flag[B]=true)writeB(victim=B)write_B(flag[B]=true) \prec write_B(victim=B)

(2)

writeA(victim=A)readA(flag[B])readA(victim)write_A(victim=A) \prec read_A(flag[B]) \prec read_A(victim)

Assumption:

(3)

writeB(victim=B)writeA(victim=A)write_B(victim=B)\prec write_A(victim=A)

Combining these results in [1] \rightarrow [3] \rightarrow [2]

Deadlock Freedom for Peterson's Lock
Starvation Freedom for Peterson's Lock

All of these proofs have only been for 2 threads, what about the nn thread case?

Filter Algorithm for nn Threads

class Filter implements Lock{
    int[] level;
    int[] victim;

    public Filter(int n){
        level = new int[n];
        victim = new int[n];
        for (int i = 1; i<n; i++){
            level[i] = 0;
        }
    } 
    
    public void lock(){
        for (int L = 1; L < n; L++){
            level[i] = L;
            victim[L] = i;
            while (there is a thread at the same or a higher level and I am the victim){/*wait*/}
        }
    }
    public void unlock(){
        level[i] = 0;
    }
}
Claim of Filter Algorithm
Induction Hypothesis

From the code:

(4)

writeB(level[B]=L)writeB(victim[L]=B)write_B(level[B]=L) \prec write_B(victim[L]=B)

(5)

writeA(victim[L]=A)readA(level[B])readA(victim[L])write_A(victim[L]=A) \prec read_A(level[B]) \prec read_A(victim[L])

By assumption that thread AA is last to write victim[L]:

(6)

writeB(victim[L]=B)writeA(victim[L]=A)write_B(victim[L]=B) \prec write_A(victim[L]=A)

Filter Starvation Free
Bounded Waiting
First-Come-First-Served
Fairness of Filter Lock

Bakery Algorithm

class Bakery implements Lock{
    boolean[] flag;
    Label[] label;

    public Bakery(int n){
        flag = new boolean[n];
        label; = new Label[n];

        for (int i = 0; i<n; i++){
            flag[i] = false;
            label[i] = 0;
        }
    }

    public void lock(){
        flag[i] = true;
        label[i] = max(label[0],...,label[n-1])+1
        while (there exist another waiting thread and that thread's label is less than mine){/*wait*/}
    }

    public void unlock(){
        flag[i] = false;
    }
}
Impracticality of the Bakery Algorithm

The Bakery Algorithm isn't widely used due to its impracticality stemming from its need for NN Distinct variables

Shared Memory

Shared read/write memory locations called Registers. Here we assume that all reads and writes are atomic

There are different types of registers:


Theorem

At least NN MRSW registers are needed to solve NN-thread deadlock-free mutual exclusion


Proof

Without NN-MRSW registers, with N threads at least one thread won't be able to express intent to enter the CS, diagrammatically:

We cannot tell if AA is in the Critical Section


The bakery algorithm actually uses 2N2N MRSW registers

What if we use MRMW registers instead?


Bad News Theorem

At least NN MRMW registers are needed to solve deadlock-free mutual exclusion


i.e. it doesn't help us


Theorem, N=2N=2

Deadlock-free mutual exclusion for 22 threads requires at least 22 MRMW registers


Proof

assume one register suffices & derive contradiction