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

Identifies the shared mutable state before proposing any primitive.

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 State
States the invariant as a sentence: "the sum of the two balances never changes".

Synchronization 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 It
Reasons about specific interleavings — "A reads 0, B reads 0, both write 1" — rather than saying "it could race".

A 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 Instinct
Distinguishes a race condition from a data race, and says which one this is.

A 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 Condition
Minimizes the critical section, and computes outside it.

Time 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 Across
Understands backpressure as a conversation, and says what a full queue does.

Block, drop or reject are three different products. A queue with no answer has chosen "grow until the process dies".

Backpressure
Bounds concurrency deliberately instead of letting it emerge from the input size.

The number of things in flight should be a decision with a reason attached, not a consequence of how many rows the query returned.

Bounding Concurrency
Considers cancellation as part of the design, not as cleanup.

Work whose result nobody wants is still holding a connection, a permit and a lock. Cancellation is how the system reclaims them.

Cancellation
Recognizes contention and can say where it is, not just that it exists.

Low 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 Costs
Measures speedup rather than assuming it, and expects it to be sublinear.

Serial 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 Half
Knows async is not parallelism, and can say what each one buys.

Async 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 Parallelism
Declines to write lock-free code without a measured reason.

Lock-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 Guarantee

Red flags11

"More threads = faster."

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 Speed
"Async = parallel."

An 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.

Async Is Not Parallelism
"JavaScript cannot do anything concurrently."

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 Runtime
"A mutex makes code thread-safe automatically."

A 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 Section
"Atomic means the whole operation is safe."

Atomic 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.

Atomics Are Not Magic
"Lock-free is always faster."

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 Guaranteed
"Deadlocks only happen with databases."

Deadlock 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 Conditions
"It worked in testing, so there is no race."

A 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 It
"Unbounded Promise.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 Concurrency
"The GIL means Python has no concurrency."

CPython'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 GIL
"Parallelism scales linearly with cores."

The 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