How to implement lock?

Version 1: Disable Interrupt

  • Simple
  • Privileged instruction => Not available in user level
  • Only works on uniprocessor

Version 2: Software Lock (Peterson’s Algorithm, N = 2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Turn: Whose turn is it?
// Flag: Who wants to enter critical section?
// flag[self] = 1 && turn = self => Holding lock
// flag[self] = 1 && turn != self => Waiting to hold lock (if flag[1-self] = 0)
// flag[self] = 0 => Unlock
int turn = 0;
int flag[2] = {0, 0};

void lock() {
    flag[self] = 1; // I need lock
    turn = 1 - self; // Give away the turn
    // Spin Wait for my turn while other thread holds the lock
    while (flag[1 - self] == 1 && turn == 1 - self);
}

void unlock() {
    flag[self] = 0;
}
  • N > 2 => Lamport’s Bakery Algorithm
  • Too hard
  • InstructionReordering matters

Version 3: Atomic Instruction

1
2
3
4
5
6
7
8
void lock() {
    while (test_and_set(&flag)); // spin wait
    while (test_and_set(&flag)) yield(); // yield
}

void unlock() {
    flag = 0;
}
  • Spin wait => Waste CPU cycle + Too much context switch
  • Yield => Still too much context switch + Starvation Possible

Version 4: Sleep Lock

  • Lock itself is a shared object => Lock for Lock
  • Do not avoid spin wait, but make wait time short
  • unlock directly transfers lock to waiting thread
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
typedef struct mutex_t {
    int flag; // Mutex state
    int guard; // guard to avoid losing wakeup
    queue_t *queue;
} mutex_t;

void lock(mutex_t *lock) {
    while (test_and_set(&lock->guard)); // spin wait 
    if (flag == 0) { // FREE
        flag = 1; // FREE -> BUSY
        lock->guard = 0;
    } else { // BUSY
        enqueue(lock->queue, self);
        lock->guard = 0;
        yield();
    }
}

void unlock(mutex_t *lock) {
    while (test_and_set(&lock->guard)); // spin wait
    if (empty(lock->queue)) { // No waiting thread
        flag = 0; // BUSY -> FREE
    } else { // Waiting thread => direct transfer
        thread_t *next = dequeue(lock->queue);
        wakeup(next);
    }
    lock->guard = 0;
}


Back