counter++ não é atômico. O processador faz READ counter, soma 1, WRITE counter de volta. Entre o READ e o WRITE de uma goroutine, a outra pode ter escrito, e o WRITE atrasado sobrescreve esse trabalho. Resultado: incrementos somem e o total fica abaixo do esperado.
| Abordagem | Custo | Quando usar |
| counter++ sem lock | quebra | nunca sob concorrência |
| atomic.AddInt64 | 1 instrução de CPU | contador ou flag simples |
| sync.Mutex | lock / unlock | operação composta (struct, mapa) |
O bug
var counter int
func main() {
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 1000; j++ {
counter++ // READ, +1, WRITE: 3 passos
}
}()
}
wg.Wait()
fmt.Println(counter) // quase nunca 2000
}
// flagra com: go run -race main.go
As correções
// atomic: uma instrução, ideal pra contador
var counter int64
atomic.AddInt64(&counter, 1)
// mutex: pra operação composta
var mu sync.Mutex
mu.Lock()
counter++
mu.Unlock()