Implementing Synchronization Primitives

In this post, we will conceptually implement four basic synchronization primitives in C: (1) spinlock, (2) semaphore and (3) condition variables. The codes are somewhat like pseudo code, and I will not actually run it.

Spinlock

Spinlock can be implemented with hardware-assisted atomic operations such as test_and_set() and compare_and_swap(). The codes are as follow.

bool test_and_set(bool* target) {
    // executed atomically
    bool ret = *target;
    *target = true;
    return ret;
}

struct spinlock {
    bool flag = false;
};

void spin_lock(struct spinlock* lock) {
    while (test_and_set(&(lock->flag))) {}
}

void spin_unlock(struct spinlock* lock) {
    flag = false;
}
bool compare_and_swap(int* value, int expected, int new_value) {
    // executed atomically
    int temp = *value;
    if (*value == expected)
        *value = new_value;
    return temp;
}

struct spinlock {
    int flag = 0;
};

void spin_lock(struct spinlock* lock) {
    while (compare_and_swap(&(lock->flag), 0, 1)) {}
}

void spin_unlock(struct spinlock* lock) {
    lock->flag = 0;
}

Semaphore

struct semaphore {
    struct spinlock lock;
    int count;
    struct queue wait_queue;
};

void wait(struct semaphore* sem) {
    spin_lock(&(sem->lock));
    if (sem->count > 0) {
        --(sem->count);
        spin_unlock(&(sem->lock));
    } else {
        --(sem->count);
        enqueue_current_thread(&(sem->wait_queue));
        spin_unlock(&(sem->lock));
        sleep_current_thread();
    }
}

void signal(struct semaphore* sem) {
    spin_lock(&(sem->lock));
    if (sem->count >= 0) {
        ++(sem->count);
    } else {
        ++(sem->count);
        dequeue_and_wake_up(&(sem->wait_queue));
    }
    spin_unlock(&(sem->lock));
}

In order for this functions to work correctly, there is an implementation issues in sleep_current_thread() and dequeue_and_wake_up() functions. Basically I tried to make all the codes of wait() and signal() to be critical sections, but there is one exception: sleep_current_thread(). I have to release the spinlock before blocking the current thread! As consequence, there might be a condition where a thread calls wait() is about to call sleep_current_thread(), and another thread calls signal() and dequeue_and_wake_up(). If the wait_queue has only one thread, it has to defer dequeue_and_wake_up() until sleep_current_thread() is called.

Condition Variable

struct condition_variable {
    struct mutex* mtx;
    struct semaphore sem;
};

void wait(struct condition_variable* cv, bool (*check)()) {
    while (!check()) {
        mutex_unlock(cv->mtx);
        wait(&(cv->sem));
        mutex_lock(cv->mtx);
    }
}

void notify_one(struct condition_variable* cv) {
    signal(&(cv->sem));
}

The code of condition variable should be verified.

Comments