Source: Operating Systems: Three Easy Pieces (OSTEP) — Chapter 28, Arpaci-Dusseau & Arpaci-Dusseau
Locks
Concurrency needs atomic critical sections, but interrupts and multiprocessors break naive sequences. A lock (mutex) lets programmers wrap a CS so it executes as if it were one atomic instruction.
lock_t mutex;
lock(&mutex);
balance = balance + 1; // critical section
unlock(&mutex);
The lock variable is free or held (exactly one owner). lock() acquires or waits; unlock() frees (or wakes a waiter). Locks give programmers a slice of scheduling control: at most one thread in that CS.
pthread mutex
POSIX: pthread_mutex_lock / unlock. Different locks for different data → fine-grained concurrency vs one coarse global lock.
Evaluating locks
| Axis | Question |
|---|---|
| Correctness | Mutual exclusion? |
| Fairness | Can a waiter starve? |
| Performance | Cost with no contention; with contention on 1 CPU; on many CPUs |
Crux: build an efficient lock — what hardware? what OS?
Controlling interrupts
Early uniprocessor trick: disable interrupts in the CS. Simple, but:
- Trusts user code with a privileged op (greedy / infinite loop = hang)
- Fails on multiprocessors
- Long mask → lost interrupts
- Slow on modern CPUs
Still used inside the OS for short kernel critical sections.
Failed attempt: plain flag
Spin while flag==1, then flag=1. Broken: test and set are not atomic — both threads can pass the test (Fig 28.2). Also spin-waiting wastes CPU (especially on one core while the holder is descheduled).
Assume interrupts at the worst moment. If a bad schedule is possible, the design is wrong.
Dekker / Peterson show locks with only loads/stores (theory interest); modern relaxed memory makes them impractical vs hardware atomics.
Test-and-set spin locks
Atomic TestAndSet(ptr, new): return old value, store new, indivisibly.
while (TestAndSet(&lock->flag, 1) == 1)
; // spin
// unlock: flag = 0
Correct ME. Not fair (possible starvation). Spin cost: painful if holder preempted on 1 CPU; OK on multi-CPU if CS is short. Needs preemption on a uniprocessor or the spinner never yields.
Compare-and-swap
CompareAndSwap(ptr, expected, new): if *ptr==expected, store new; return actual. Spin lock: wait until CAS from 0→1 succeeds. More powerful than TAS (later: lock-free structures).
Load-linked / store-conditional
LL loads; SC stores only if no intervening write to that address. Lock: spin until LL sees 0, then SC(1); retry on SC fail. Only one of two racing SC succeeds.
Fetch-and-add: ticket lock
Atomic FetchAndAdd returns old and increments. Ticket lock [MS91]: myturn = FetchAndAdd(&ticket); spin until turn == myturn; unlock does turn++. Fair progress once you hold a ticket — unlike plain TAS.
Rank the sequence
1 / 1Order the steps a thread performs to acquire and release a ticket lock.
OrderDrag the handle to rearrange — top should be first.
- 1Enter the critical section
- 2Call FetchAndAdd on ticket to get myturn
- 3Increment turn (unlock)
- 4Spin while turn != myturn
Drag items into the correct order (or use ↑↓), then check.
Too much spinning → yield, then sleep
On one CPU, spinning for a preempted holder wastes whole timeslices.
- Yield while lock held — better than pure spin, still thrashing with many waiters; starvation remains.
- Queue + park/unpark (Solaris-style, Fig 28.9): short guard spin-lock around flag/queue; failed acquire → enqueue, release guard, park. Unlock unparks next (often pass the lock without clearing flag). Use setpark() to avoid the wakeup/waiting race.
- Linux futex: wait/wake on an address; fast path when uncontended; waiter count packed with lock bit.
- Two-phase locks: spin briefly, then sleep (hybrid).
Rank the sequence
1 / 1Order a thread's steps when it fails to acquire a queue + park/unpark lock (Fig 28.9).
OrderDrag the handle to rearrange — top should be first.
- 1Park (go to sleep)
- 2Release the guard spin-lock
- 3Acquire the guard spin-lock and find the flag held
- 4Add itself to the waiting queue
Drag items into the correct order (or use ↑↓), then check.
High-priority spinner blocked on low-priority holder → hang (Pathfinder). Mitigations: avoid long spins, priority inheritance, or equal priorities.
Interactive
Try: lock API · broken flag · TAS · ticket · park/queue.
Check your understanding
Locks
1 / 6A mutex’s main job is to…
Pick an answer to see if you are right.
Coding challenges
Homework lab: x86.py locks
Use the OSTEP locks homework (flag.s, test-and-set.s, peterson.s, ticket.s, yield.s, …).
Homework: x86.py locks
0/5 startedSummary
| Approach | ME? | Fair? | Notes |
|---|---|---|---|
| Interrupt disable | Uni yes | — | Not for apps / MP |
| Plain flag | No | — | Race on test/set |
| TAS / CAS spin | Yes | Poor | OK if CS short + multi-CPU |
| Ticket (FAA) | Yes | Better | Ordered tickets |
| Yield while held | Yes | Poor | Less spin, still waste |
| Queue + park/futex | Yes | Controllable | Real libraries; two-phase hybrid |
Previous: Concurrency — An Introduction
Next: Lock-based Concurrent Data Structures — counters, lists, queues, hash tables.
Further Reading
- Remzi & Andrea Arpaci-Dusseau — Operating Systems: Three Easy Pieces Ch. 28
- OSTEP threads-locks PDF
- OSTEP threads-locks homework
- Mellor-Crummey & Scott [MS91]; Linux nptl / futex [L09]