Data Race vs Race Condition in Go: Clear Differences and How to Fix Them
The article explains the distinction between a data race—simultaneous unsynchronized memory access by goroutines—and a race condition—logic errors caused by timing dependencies—using Go code examples, demonstrates how to reproduce each issue, and shows how mutexes or atomic operations can resolve them.
Data Race
A data race occurs when multiple goroutine(s) access the same memory location concurrently and at least one of the accesses is a write.
package main
import (
"fmt"
"sync"
)
func main() {
counter := 0
var wg sync.WaitGroup
// Launch 100 goroutine(s)
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter++ // Data race: concurrent read‑write on counter
}()
}
wg.Wait()
fmt.Println("Counter:", counter) // Result nondeterministic, may be wrong
}
// Solution: use a mutex or atomic operation
func main() {
counter := 0
var wg sync.WaitGroup
var mu sync.Mutex
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}()
}
wg.Wait()
fmt.Println("Counter:", counter) // Correct result: 100
}Race Condition
A race condition is a higher‑level bug where program correctness depends on the relative timing of multiple threads; it can exist even without a data race.
package main
import (
"fmt"
"sync"
"time"
)
type Account struct {
balance int
mu sync.Mutex
}
func main() {
account := &Account{balance: 100} // initial balance 100
var wg sync.WaitGroup
// Even with a mutex protecting the balance, the check‑then‑act logic is not atomic
wg.Add(2)
go func() {
defer wg.Done()
// First withdrawal
if account.balance >= 100 { // check
time.Sleep(time.Millisecond) // simulate processing time
account.mu.Lock()
account.balance -= 100 // withdraw 100
account.mu.Unlock()
}
}()
go func() {
defer wg.Done()
// Second withdrawal
if account.balance >= 100 { // check
time.Sleep(time.Millisecond) // simulate processing time
account.mu.Lock()
account.balance -= 100 // withdraw 100
account.mu.Unlock()
}
}()
wg.Wait()
fmt.Println("Final balance:", account.balance)
}Main Differences
Data Race :
Low‑level concept focusing on memory access.
Occurs when multiple goroutine(s) simultaneously access the same memory location, with at least one write.
Can be avoided with mutexes or atomic operations.
Go provides the -race detector to find data races.
Always indicates a bug in the program.
Race Condition :
Higher‑level concept focusing on program logic.
Program correctness depends on the ordering of events.
Can exist even without a data race.
Harder to detect and fix.
Often stems from design issues.
In short, a data race concerns concurrent reads/writes to the same memory location, while a race condition concerns the program’s behavior depending on the relative order of events.
BirdNest Tech Talk
Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
