Β§28.9–28.11Compare-And-Swap … Fetch-And-Add

Part II OSTEP pp. 323–326 Β· ~5 min read

  • compare-and-swap
  • load-linked/store-conditional
  • fetch-and-add
  • ticket lock

Test-and-set is one citizen of a larger family. Three more atomic instructions β€” each a different superpower, one of which finally buys fairness.

28.9 Compare-And-Swap

Compare-and-swap (SPARC) or compare-and-exchange (x86), as C pseudocode (Figure 28.4):

int CompareAndSwap(int *ptr, int expected, int new) {
    int original = *ptr;
    if (original == expected)
        *ptr = new;
    return original;
}

Update only if the current value matches your expectation; either way, learn what was there. A spin lock needs just:

void lock(lock_t *lock) {
    while (CompareAndSwap(&lock->flag, 0, 1) == 1)
        ; // spin
}

Used this way it behaves identically to test-and-set. But CAS is the more powerful instruction β€” a power the book cashes in later, with a peek at lock-free synchronization.

28.10 Load-Linked And Store-Conditional

MIPS (and Alpha, PowerPC, ARM) split the atom differently β€” a pair of instructions working in concert (Figure 28.5):

int LoadLinked(int *ptr) {
    return *ptr;
}

int StoreConditional(int *ptr, int value) {
    if (no update to *ptr since LoadLinked to this address) {
        *ptr = value;
        return 1; // success!
    } else {
        return 0; // failed to update
    }
}

Load-linked is an ordinary load; store-conditional succeeds only if nothing stored to that address in between β€” returning 1 on success, 0 on failure. The lock (Figure 28.6 β€” try building it yourself first!):

void lock(lock_t *lock) {
    while (1) {
        while (LoadLinked(&lock->flag) == 1)
            ; // spin until it's zero
        if (StoreConditional(&lock->flag, 1) == 1)
            return; // if set-it-to-1 was a success: all done
                    // otherwise: try it all over again
    }
}

void unlock(lock_t *lock) {
    lock->flag = 0;
}

The failure case is the instructive one: two threads both LL a flag of 0, both race to SC β€” and exactly one succeeds; the other’s SC fails (a store intervened since its LL) and it loops back to try again. Undergraduate David Capel’s short-circuit version, for connoisseurs of concision:

void lock(lock_t *lock) {
    while (LoadLinked(&lock->flag) ||
          !StoreConditional(&lock->flag, 1))
        ; // spin
}

Tip: Less Code Is Better Code (Lauer’s Law)

Brag about how LITTLE code you wrote, not how much. Short, concise code is easier to understand and has fewer bugs. As Hugh Lauer said of building the Pilot OS: β€œIf the same people had twice as much time, they could produce as good of a system in half the code.” Next time you finish an assignment: go back, rewrite, make it clear and concise.

28.11 Fetch-And-Add

One last primitive: fetch-and-add β€” atomically increment, return the old value:

int FetchAndAdd(int *ptr) {
    int old = *ptr;
    *ptr = old + 1;
    return old;
}

From it, Mellor-Crummey and Scott built the ticket lock (Figure 28.7):

typedef struct __lock_t {
    int ticket;
    int turn;
} lock_t;

void lock(lock_t *lock) {
    int myturn = FetchAndAdd(&lock->ticket);
    while (lock->turn != myturn)
        ; // spin
}

void unlock(lock_t *lock) {
    lock->turn = lock->turn + 1;
}
The ticket lock: take a number, wait to be served β€” no one waits forever
ticket (next)0turn (serving)0Thread Ano ticket yetidleThread Bno ticket yetidleThread Cno ticket yetidle

1Fresh lock: ticket 0, turn 0

Two counters do all the work: ticket is the next number the dispenser will hand out; turn is the number currently being served. Three threads approach.

step 1 / 5

The deli counter, in silicon β€” and the first design on the fairness axis’s good side. But every lock so far still spins, and spinning has a price the next section finally refuses to pay.

Check yourself

1.Compare-and-swap vs test-and-set: what's the behavioral difference, and does it matter for a simple spin lock?

2.With load-linked/store-conditional, two threads both LL the flag (both see 0) and both attempt SC. What happens?

3.Trace the ticket lock: ticket=0, turn=0. A calls lock(), then B, then C; then A unlocks.

4.What property does the ticket lock provide that TAS/CAS/LL-SC spin locks fundamentally lack?

5.Lauer's Law, as the book names it:

5 questions