Synchronization 2: Critical sections, serializability, lock granularity

Overview

In this lecture, we discuss multithreading, low-level synchronization, and synchronization objects.

Full notes on synchronization

Increment recap

Atomic data types address data races

Serializability

Serializable increment

incr-atomic.cc

Atomic data types are necessary, but not sufficient

CS 61 Bank

Basic account operations

C++ sketch

class acct {
    long bal;

    void deposit(long amt) {
        bal += amt;
    }

    bool try_withdraw(long amt) {
        if (bal < amt) {
            return false;
        }
        bal -= amt;
        return true;
    }
};

Concurrent access

Atomics eliminate data races

class acct {
    std::atomic<long> bal;

    // ... same `deposit` and `try_withdraw`
};

Atomics don’t eliminate all race conditions

Race condition example

Critical sections

How to delimit a critical section

Global mutex

std::mutex bm;

class acct {
    std::atomic<long> bal;

    void deposit(long amt) {
        bm.lock();
        bal += amt;
        bm.unlock();
    }

    bool try_withdraw(long amt) {
        bm.lock();
        if (bal < amt) {
            bm.unlock();
            return false;
        }
        bal -= amt;
        bm.unlock();
        return true;
    }
};

Functions and lock state

Guard pattern

Mutexes and atomics

std::mutex bm;

class acct {
    long bal;

    void deposit(long amt) {
        std::unique_lock guard(bm);
        bal += amt;
    }

    bool try_withdraw(long amt) {
        std::unique_lock guard(bm);
        if (bal < amt) {
            return false;
        }
        bal -= amt;
        return true;
    }
};

Lock granularity

Fine-grained account locking

class acct {
    long bal;
    std::mutex am;

    void deposit(long amt) {
        std::unique_lock guard(am);
        bal += amt;
    }

    bool try_withdraw(long amt) {
        std::unique_lock guard(am);
        if (bal < amt) {
            return false;
        }
        bal -= amt;
        return true;
    }
};

Compare–exchange

class acct {
    std::atomic<long> bal;

    void deposit(long amt) {
        bal += amt;
    }

    bool try_withdraw(long amt) {
        long tbal = bal;
        while (true) {
            if (tbal < amt) {
                return false;
            }
            if (bal.compare_exchange_strong(tbal, tbal - amt)) {
                return true;
            }
        }
    }
}