Concurrency Signals
What strong and weak concurrency reasoning sound like. Green flags name the invariant before the primitive, describe an interleaving that breaks it, and say what the concurrency costs. Red flags are the sentences that precede a bug nobody can reproduce.
Green flags12
Every concurrency bug lives in state that two things touch and at least one changes. Naming it first is what makes the rest of the conversation possible — and sometimes the answer is that nothing is shared.
Shared Mutable StateSynchronization exists to preserve an invariant. Without one you can only say a region is locked, not that the code is correct.
Invariants: Name It Before You Lock ItA race is demonstrated by exhibiting a schedule. Producing one is the difference between suspecting a bug and having found it.
Reasoning About Races: A Method, Not an InstinctA data race is unsynchronized conflicting memory access under a memory model — in C++ that is undefined behaviour. A race condition is logical correctness that depends on timing, and it exists in languages that have no data races at all.
Data Race Is Not Race ConditionTime under the lock is multiplied by every waiter. Moving a JSON parse out of a locked region often does more for latency than any lock-free rewrite would.
Lock Scope: What You Hold It AcrossBlock, drop or reject are three different products. A queue with no answer has chosen "grow until the process dies".
BackpressureThe number of things in flight should be a decision with a reason attached, not a consequence of how many rows the query returned.
Bounding ConcurrencyWork whose result nobody wants is still holding a connection, a permit and a lock. Cancellation is how the system reclaims them.
CancellationLow CPU with high latency has a specific shape. Pointing at the acquisition site — and its hold time — is what turns it into a fix.
What Contention Actually CostsSerial fractions, synchronization, memory bandwidth and cache effects all take their cut. The interesting question is where the missing speedup went.
Why Eight Cores Give You Four and a HalfAsync overlaps waiting on one thread; parallelism executes computation simultaneously on several cores. Confusing them produces both dead event loops and pointless thread pools.
Async Is Not ParallelismLock-free is a progress guarantee. It is harder to write, harder to review, harder to test, and frequently slower under contention than a well-scoped mutex.
Lock-Free Is a Progress GuaranteeRed flags11
Threads beyond the number of runnable cores add context switches, cache pollution and scheduler overhead without adding execution. If the work is I/O-bound the limit is downstream capacity; if it is CPU-bound the limit is cores. Neither limit moves because you raised a number.
More Threads Is Not More SpeedAn event loop runs one piece of your code at a time. await yields so something else can run while this task waits — that is overlap, not simultaneity. Two awaited CPU-bound functions still take the sum of their durations.
The event loop runs one task at a time, but I/O, timers and many platform APIs proceed outside it, and worker threads and web workers run real parallel code on other cores. Single-threaded describes where *your* callbacks run, not what the runtime is doing.
JavaScript: One Event Loop per Agent, Not One Thread per RuntimeA mutex serializes a region. It says nothing about whether the region is the right one. Locking each of three fields separately still lets another thread observe an inconsistent combination of the three.
Finding the Critical SectionAtomic applies to one operation on one variable. if (map.get(k) == null) map.put(k, v) on an atomic map is two atomic operations with a gap in the middle, and the gap is where the second thread goes.
Lock-free means some thread always makes progress — a liveness property, not a speed one. Under contention, CAS loops retry, burn CPU and generate cache-line traffic; a mutex that blocks can be measurably faster and is far easier to get right.
Wait-Free vs Lock-Free: Whose Progress Is GuaranteedDeadlock needs mutual exclusion, hold-and-wait, no preemption and circular wait. Two mutexes taken in opposite orders satisfies all four, and so does a thread pool where every worker is waiting on a task that is queued behind it.
The Four ConditionsA test run explores one schedule out of an enormous space, and on a lightly loaded machine it is the same schedule every time. Absence of a failure is evidence about the schedules you ran, not about the ones production will find.
Heisenbugs: The Bug That Leaves When You Look at ItPromise.all is fine."The array is a list of promises that have already started. Ten thousand entries means ten thousand simultaneous sockets, ten thousand response bodies in memory, and a downstream service receiving your whole input at once.
Unbounded ConcurrencyCPython's global interpreter lock prevents threads from executing Python bytecode simultaneously; it is released around I/O and inside many C extensions, so threaded I/O overlaps perfectly well. Concurrency is fine — it is CPU-bound *parallelism* that needs processes, and even that is changing with the free-threaded builds.
Python: Threads, Processes and the GILThe serial fraction caps it (Amdahl), synchronization takes a cut, memory bandwidth saturates, and threads bouncing between cores destroy cache locality. Eight cores giving five times the throughput is a normal, good result.
Amdahl's Law