Implement a Mutual-Exclusion Lock Without Built-In Locks
AppleSoftware Engineer · OnsiteLast updated
Implement a mutual-exclusion lock without any built-in lock, mutex or semaphore, relying only on atomic operations. Tests atomic read-modify-write, memory ordering, spinning versus sleeping waiters, fairness, reentrancy, and software-only mutual exclusion when atomic instructions are unavailable.
Implement a lock: a mutual-exclusion primitive with lock() and unlock(). You may not call any existing lock, mutex, semaphore, monitor or condition variable from the language or a library; the point is to build one. After the implementation, expect fundamentals questions about what your lock relies on in the hardware and the operating system.
Lock()
lock() -> None # returns once the calling thread holds the lock
unlock() -> None # releases the lock; only the holder calls it
Constraints and Clarifications
- At most one thread may hold the lock at any time.
- If the lock is free and threads are trying to take it, one of them must eventually succeed.
- Writes made inside one critical section must be visible to the next thread that acquires the lock.
Clarifying Questions
- Which atomic operations may I assume: compare-and-swap, test-and-set, fetch-and-add, or only ordinary loads and stores?
- Is the number of threads fixed and known (for example, exactly two), or arbitrary?
- Should waiting threads spin or sleep? If they sleep, may I use a low-level operating-system wait and wake facility, as long as it is not itself a lock?
- Must the lock be fair (first come, first served)? Must it be reentrant?
- Which language and memory model?
Hints
Hint 1
The race in check-then-take: Consider what goes wrong if two threads both see the lock as free before either marks it as taken, and what would make checking and taking a single indivisible step.
Hint 2
What waiters do with their time: Once the lock is correct, think about what a waiting thread does with its CPU while the holder is running, or while the holder has been descheduled.
Follow-up questions
- Make the lock first come, first served. What does that cost when many cores contend?
- Make it reentrant, so that the holder can call `lock()` again without deadlocking itself.
- Make waiting threads sleep instead of spin. How do you avoid a wakeup that is lost between deciding to sleep and actually sleeping?
- Without atomic read-modify-write instructions, how can two threads still achieve mutual exclusion, and why does that algorithm fail on modern processors without extra fences?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.