Lecture 22: Synchronization II

Ticket lock

struct ticket_lock {
    std::atomic<unsigned> now_ = 0;
    std::atomic<unsigned> next_ = 0;

    void lock() {
        unsigned me = next_++;
        while (me != now_) {
            pause();
        }
    }
    void unlock() {
        now_++;
    }
};

MCS (Mellor-Crummey Scott) lock

MCS lock I (guard-style)

struct mcs_lock {
    std::atomic<mcs_lock_guard*> tail_;  // nullptr if unlocked
};

struct mcs_lock_guard {     // represents a thread waiting for/holding lock
    std::atomic<mcs_lock_guard*> next_ = nullptr;
    std::atomic<bool> blocked_ = false;
    mcs_lock& lock_;


    mcs_lock_guard(mcs_lock& lock)
        : lock_(lock) {     // lock
        mcs_guard* prev_tail = lock_.tail_.exchange(this); // mark self as tail
        if (prev_tail) {    // must wait for previous tail to exit
            blocked_ = true;
            prev_tail->next_ = this;
            while (blocked_) {
                pause();
            }
        }
    }

    ~mcs_lock_guard() {     // unlock
        if (!next_) {
            mcs_lock_guard* expected = this;
            if (lock_.compare_exchange_strong(expected, nullptr)) {
                return;
            }
        }
        while (!next_) {
            pause();
        }
        next_->blocked_ = false;
    }
};

// some function that uses a lock `l`
f() {
    ...
    {
        mcs_lock_guard guard(l);
        ... critical section ...
    }
    ...
}

About MCS

Variants of MCS

ShflLocks

ShflLock benchmark

Beyond shuffling

Delegation

Architecture of ffwd system

ffwd Performance

Readers/writer locks

Readers/writer implementation

struct rw_lock {
    std::atomic<int> val_;

    void lock_read() {
        int expected = val_;
        while (expected < 0
               || !val_.compare_exchange_weak
                        (expected, expected + 1)) {
            pause();
            expected = val_;
        }
    }
    void unlock_read() {
        --val_;
    }

    void lock_write() {
        int expected = 0;
        while (!val_.compare_exchange_weak(expected, -1)) {
            pause();
            expected = 0;
        }
    }
    void unlock_write() {
        val_ = 0;
    }
};

rwlock variant: Reduce memory contention

struct rw_lock_2 {
    spinlock f_[NCPU];     // would really want separate cache lines

    void lock_read() {
        f_[this_cpu()].lock();
    }
    void unlock_read() {
        f_[this_cpu()].unlock();
    }

    void lock_write() {
        for (unsigned i = 0; i != NCPU; ++i) {
            f_[i].lock();
        }
    }
    void unlock_write() {
        for (unsigned i = 0; i != NCPU; ++i) {
            f_[i].unlock();
        }
    }
};

rwlock variant: Fairness

struct ticket_rwlock {
    std::atomic<unsigned> now_ = 0;
    std::atomic<unsigned> read_now_ = 0;
    std::atomic<unsigned> next_ = 0;

    void read_lock() {
        unsigned me = next_++;
        while (me != read_now_) {
            pause();
        }
        read_now_++;
    }
    void read_unlock() {
        now_++;
    }

    void write_lock() {
        unsigned me = next_++;
        while (me != now_) {
            pause();
        }
    }
    void write_unlock() {
        read_now_++;
        now_++;
    }
};