Locks, semaphores, conditions
Six synchronisation primitives, each shown first as the failure you get without it, then as the guarantee it makes, then as the classic way people misuse it, with every claim reproduced by a schedule the build records.
Why does Condition.wait have to sit in a while loop and not an if?
Because waking up tells you the lock is back, not that the thing you were waiting for is still there. A notify marks every waiter it names, and each of them then has to queue for the lock again; by the time the second one holds it, the first may have taken the only item. The threading documentation says the loop is necessary because wait can return after an arbitrarily long time and the condition that prompted the notify may no longer hold. Section 04 shows the if version raising IndexError and the while version going back to waiting, under the same recorded schedule. Condition.wait_for(predicate) writes the loop for you and is the version to type in a round.
Is list.append thread-safe in CPython, and can I skip the lock?
It is, today, and you should still take the lock. The Python FAQ lists append, extend, x = L[i], L.pop() and a few dict operations as atomic, but the free-threading HOWTO says plainly that Python has never guaranteed behaviour for concurrent modification of built-in types, so this describes the current implementation rather than a promise. The FAQ also lists i = i + 1 and D[x] = D[x] + 1 as not atomic, and almost every real use is a pair of operations rather than one. In a round the answer that scores is: yes in CPython today, and I would take the lock anyway, because the next reader of this file cannot tell which lines were safe on purpose.
When do I actually need an RLock?
When one method that holds the lock calls another method that takes the same lock. With a plain Lock that is a deadlock of a thread with itself, which section 02 reproduces in five turns. An RLock fixes it by counting the depth and letting its own holder back in. Needing one is usually a sign that your public methods both guard and call each other, and the smaller fix is a private helper that assumes the lock is already held. Say both out loud: the RLock works, and the layering is what made it necessary.
Event or Condition?
An Event when the answer is a single fact that becomes true once and stays true: the service is up, the shutdown has been asked for, the setup has finished. Every waiter goes through on one set, nobody holds anything, and there is no predicate and no lock to reason about. A Condition when what you are waiting for is a state that can change back, such as a queue being non-empty or a buffer having room, because then you need the lock that guards the state and a predicate you re-check under it. Using an Event to hand work over is the classic mistake, and section 05 proves it loses an item: a flag is not a counter.
The standard library has no reader-writer lock. Should I write one in an interview?
Only when the workload is genuinely read-heavy and you can say so, and then write it in stages so the interviewer follows. Section 06 builds it from one Condition: a count of readers, a flag for the writer, and one predicate per side. The part that earns the marks is naming the fairness problem before you are asked. A steady stream of readers can starve a writer for ever, because a new reader only waits when somebody is writing, and the fix is to make readers also wait while a writer is queued. The build records the cost under one schedule: six reads and no write with reader preference, two reads and the write with writer preference.