A race condition is a concurrency error in which a program’s result depends on the timing of independent events. It occurs when concurrent executions share a resource without proper synchronization and interfere with one another, producing unexpected behavior, data corruption, or a crash that may be hard to reproduce. section 3.3 section 9.1
Shared memory makes the risk concrete: one goroutine can write a value, then find that another goroutine has changed it before it reads it again. The instructions that must run without such interference form a critical section. section 3.2.3 section 3.5
A bank balance that changes by chance
Two goroutines share a bank account starting at 100. One adds 10 and the other subtracts 10, each one million times, so the expected final balance is 100. Without synchronization, repeated runs can instead leave millions above zero or below zero because both goroutines can overlap while updating the same variable. section 3.3.1
Treat these as warning signs and responses:
- Look for goroutines that read or write the same variable, slice, or other shared resource concurrently. section 3.2.3 section 3.3.3
- Do not rely on yielding with runtime.Gosched() to fix a race; it can change how often the error appears but provides no protection. section 3.3.2
- Choose a coordination approach appropriate to the problem: protect shared critical sections or avoid unnecessary shared memory through communication. section 3.3.3 section 1.5.2
Listing 4.1 Stingy’s and Spendy’s functions using a mutex
package main import ( "fmt" "sync" "time" ) func stingy(money *int, mutex *sync.Mutex) { #1 for i := 0; i < 1000000; i++ { mutex.Lock() #2 *money += 10 mutex.Unlock() #3 } fmt.Println("Stingy Done") } func spendy(money *int, mutex *sync.Mutex) { #1 for i := 0; i < 1000000; i++ { mutex.Lock() #2 *money -= 10 mutex.Unlock() #3 } fmt.Println("Spendy Done") } #1 Accepts a pointer to the shared mutex struct #2 Locks the mutex before entering the critical section #3 Unlocks after exiting the critical section
A mutex brackets each update with Lock and Unlock, allowing only one goroutine to modify the shared balance at a time. section 4.1.1
In Go, sync.Mutex provides Lock() and Unlock() for exclusive access to a critical section. A goroutine that reaches an already locked mutex waits until it is released; protecting both writes and reads of the shared balance produces the expected result. Go’s race detector can also help spot races in code. section 4.1.1 section 3.5
Channels and communicating sequential processes offer another model: isolated goroutines communicate and synchronize by sending messages. This can reduce race-condition risk, although shared-memory primitives such as mutexes and condition variables may be a better-performing choice for some problems. section 1.5.2



















