4.2.15Operating Systems

Mutex — implementation using hardware atomics (test-and-set, CAS)

2,199 words10 min readdifficulty · medium

WHY do we even need hardware help?

The fix: a single CPU instruction that reads the old value AND writes a new value in one atomic, uninterruptible step, with the bus locked so no other core can sneak in.


WHAT is an atomic instruction?


test-and-set: derive the lock from scratch

HOW to build a spinlock from TAS

WHY this is correct. test_and_set always sets lock = 1. The return value tells us what it used to be:

  • If old value was 0 → the lock was free, and we just claimed it. Loop exits → we own it.
  • If old value was 1 → someone already held it; we set it to 1 again (harmless, it was already 1) and keep spinning.

The "check" (read old) and the "claim" (write 1) are now one indivisible action, so the race from above cannot happen. There is no window between read and write for another thread to slip through.


compare-and-swap (CAS): the more powerful primitive

WHY CAS > TAS. TAS can only manipulate a boolean. CAS lets you say "change X only if it's still the value I last saw." This enables lock-free data structures (counters, stacks, queues), not just mutexes. It's the foundation of AtomicInteger.compareAndSet, std::atomic::compare_exchange, etc.

Figure — Mutex — implementation using hardware atomics (test-and-set, CAS)

Worked examples



Flashcards

Why can't a plain while(lock){} lock=1 implement a mutex?
The read of lock and the write are two separate instructions; a context switch between them lets two threads both see "free" and both enter — breaking mutual exclusion.
What does test_and_set(addr) do atomically?
Reads old value at addr, sets *addr = 1, returns the old value — all as one indivisible step.
In a TAS spinlock, what does a returned old value of 0 mean?
The lock was free and you just acquired it; exit the spin loop.
What does compare_and_swap(addr, expected, new) do?
Atomically: if *addr == expected, set *addr = new and return true; else leave it and return false.
Why is CAS more powerful than test-and-set?
CAS conditions the write on the current value matching an expected one, enabling lock-free data structures; TAS only flips a boolean.
What is the ABA problem?
A CAS succeeds because the value equals the expected one, but it actually changed A→B→A in between; CAS can't detect the intermediate change. Fix with version tags.
Why does Test-and-Test-and-Set reduce bus traffic?
It spins on a cheap cached read and only issues the expensive atomic RMW when the lock looks free, avoiding cache-line bouncing.
Is busy-wait spinning always bad?
No — for very short critical sections on multi-core, spinning avoids context-switch overhead and is faster than sleeping.
Why is volatile insufficient for a lock flag?
It prevents compiler caching but does NOT provide atomic read-modify-write or memory-ordering guarantees.
What memory ordering does acquire/release need?
Acquire needs an acquire barrier (no later ops move before it); release needs a release barrier (no earlier ops move after it) so critical-section writes are visible before unlock.

Recall Feynman: explain to a 12-year-old

Imagine one bathroom and a sign on the door that says FREE or BUSY. If two kids both glance at the sign at the same instant, both see FREE and both barge in — chaos. The trick: a magic door handle that, in one snap, looks at the sign AND flips it to BUSY, then tells you what the sign said before. If it said FREE, you got in. If it said BUSY, someone beat you — wait outside. Because looking and flipping happen together with no gap, two kids can never both win. That magic handle is "test-and-set." A fancier handle ("CAS") flips the sign to BUSY only if it's still exactly FREE — letting you safely change shared things without a full lock.

Connections

  • Critical Section Problem — the race this solves
  • Spinlock vs Blocking Mutex — what to do while waiting
  • Memory Barriers and Ordering — making the lock visible across cores
  • Lock-Free Data Structures — built on CAS
  • Semaphores — higher-level counting synchronization
  • Cache Coherence (MESI) — why atomics cause cache-line bouncing
  • Deadlock — what mutual exclusion can lead to

Concept Map

read then write splits into

breaks

glues read+write into one step

locks cache line / bus

type of

type of

sets flag to 1, returns old value

provides

tells thread if it won lock

spin while old was 1

guarantees

Naive boolean lock

Race condition

Mutual exclusion

Atomic RMW instruction

No intermediate state

test-and-set

compare-and-swap

TAS spinlock

Old return value

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, mutex ka core idea bahut simple hai: ek shared flag hota hai jo batata hai "lock free hai ya busy". Problem yeh hai ki normal code mein flag ko padhna (read) aur set karna (write) do alag-alag machine instructions hote hain. In dono ke beech mein CPU thread switch kar sakta hai. Result? Do threads dono "free" dekh lete hain, dono andar ghus jaate hain — mutual exclusion toot gaya. Yeh bug isliye chhupa rehta hai kyunki code sequential dikhta hai, par hardware level pe read aur write glued nahi hai.

Solution: hardware ek atomic instruction deta hai jo read aur write dono ko ek hi indivisible step mein kar deta hai, bus ko lock karke. test-and-set (TAS) flag ko 1 set karta hai aur uski purani value return karta hai — agar purani value 0 thi to tum lock jeet gaye, agar 1 thi to koi aur ke paas hai, spin karo. Return value hi sab kuch hai — wahi batata hai ki tumne lock liya ya nahi.

CAS (compare-and-swap) thoda powerful hai: "value ko tabhi change karo jab woh abhi bhi expected value ke barabar ho." Isse na sirf mutex banta hai, balki lock-free counters, stacks, queues bhi ban jaate hain. AtomicInteger.compareAndSet, std::atomic — sab isi pe khade hain. Bas ek catch: ABA problem — value A se B aur wapas A ho jaaye to CAS ko pata nahi chalta; iske liye version tags use karte hain.

Yaad rakhne wali baat: R-M-W must be ONE — Read, Modify, Write ko indivisible banao, bas yahi pura mutex ka raaz hai. volatile kaafi nahi hai (woh atomicity nahi deta), aur spinning hamesha bura nahi hota — chhoti critical section ke liye spin karna sleep karne se fast hai.

Test yourself — Operating Systems

Connections