Runtimegoconcurrencyrace-conditionatomicsruntimemedium

100,000 requests in. The counter says 97,832 — every run, a different shortfall.

01Symptom

A single `counter++` in a Go HTTP handler backs a site-wide hit counter. Under load it is never exact: 100,000 requests produce 97,832 the first run, 98,041 the second. The shortfall changes every run and is never above the true count. No database, no unharnessed resource — just one shared `int` and the default net/http server.

02Constraints

  • Go's default `net/http` server: one goroutine per incoming request — nothing pools or serializes handlers
  • Load test sends exactly 100,000 requests with high concurrency — thousands in flight at once
  • Single machine, multi-core
  • No locks, no atomics, no channels: the handler is exactly the shown `counter++`
  • The counter must be exactly correct, and the fix is not acceptable without naming the mechanism

03Evidence

  • Two identical load runs land on different shortfalls (97,832 then 98,041), and neither ever exceeds 100,000
  • `go build -race` reports an unsynchronized read/write on `counter` on the first request batch
  • amd64 disassembly of the handler shows three instructions for the increment: a load into a register, an add of one, and a store back — not a single in-memory increment
  • Verified at low concurrency (one request at a time) the counter is exact for all 100,000 — the loss scales with overlap, not with volume

→The question

`counter++` looks like one operation in the source. It is not one at the machine level — walk through what the CPU actually does, where two goroutines can interleave, why the loss only shows under concurrency and varies run to run, and how you would fix it and at what cost.

04Your prediction

01At the machine level, what does `counter++` actually do?
02At which point can two goroutines interleave?
03Why does the counter land short every run, with a different shortfall each time?
04What correctly fixes it, and what does the fix cost on x86-64?
0 / 600 chars