# How to Fix Go Race Condition Detected
Race conditions occur when multiple goroutines access shared data concurrently without proper synchronization. Go's race detector helps identify these dangerous bugs.
Error Pattern
```text ================== WARNING: DATA RACE Write at 0x00c0000b0018 by goroutine 8: main.increment() /path/main.go:15 +0x84
Previous read at 0x00c0000b0018 by goroutine 7: main.getValue() /path/main.go:20 +0x44
Goroutine 8 (running) created at: main.main() /path/main.go:10 +0xc4 ================== ```
Common Race Condition Patterns
Pattern 1: Shared Variable Without Synchronization
```go // RACE CONDITION! var counter int
func increment() { counter++ // Multiple goroutines access unsafely }
func main() { for i := 0; i < 1000; i++ { go increment() } time.Sleep(time.Second) fmt.Println(counter) // Expected 1000, but varies } ```
Pattern 2: Map Concurrent Access
```go // RACE CONDITION! var m = make(map[string]int)
func write(key string, val int) { m[key] = val // Concurrent map write }
func read(key string) int { return m[key] // Concurrent map read } ```
Pattern 3: Struct Field Access
```go type Counter struct { value int }
func (c *Counter) Add() { c.value++ // Race if called from multiple goroutines } ```
Detection
Run with Race Detector
```bash # Build with race detection go build -race
# Run with race detection go run -race main.go
# Test with race detection go test -race ./... ```
Enable Race Detection in Tests
```go // In test files, race detection is enabled by default with go test -race func TestCounter(t *testing.T) { var c Counter
for i := 0; i < 100; i++ { go c.Add() }
// Race detector will catch this } ```
Solutions
Solution 1: Use Mutex
```go import "sync"
type SafeCounter struct { mu sync.Mutex value int }
func (c *SafeCounter) Add() { c.mu.Lock() defer c.mu.Unlock() c.value++ }
func (c *SafeCounter) Value() int { c.mu.Lock() defer c.mu.Unlock() return c.value } ```
Solution 2: Use RWMutex for Read-Write Separation
```go type SafeCounter struct { mu sync.RWMutex value int }
func (c *SafeCounter) Add() { c.mu.Lock() // Write lock c.value++ c.mu.Unlock() }
func (c *SafeCounter) Value() int { c.mu.RLock() // Read lock (allows concurrent reads) defer c.mu.RUnlock() return c.value } ```
Solution 3: Use sync.Map for Concurrent Maps
```go import "sync"
var m sync.Map
func write(key string, val int) { m.Store(key, val) // Thread-safe }
func read(key string) (int, bool) { val, ok := m.Load(key) // Thread-safe if ok { return val.(int), true } return 0, false }
// Range over map m.Range(func(key, value interface{}) bool { fmt.Println(key, value) return true // Continue iteration }) ```
Solution 4: Use Atomic Operations
```go import "sync/atomic"
var counter int64
func increment() { atomic.AddInt64(&counter, 1) // Atomic increment }
func getValue() int64 { return atomic.LoadInt64(&counter) // Atomic read }
// For pointers var ptr atomic.Value ptr.Store(someValue) loaded := ptr.Load() ```
Solution 5: Use Channels for Communication
```go func counterService() (chan<- int, <-chan int) { increments := make(chan int) values := make(chan int)
go func() { var counter int for { select { case inc := <-increments: counter += inc case values <- counter: // Send current value } } }()
return increments, values }
// Usage inc, val := counterService() inc <- 1 // Increment current := <-val // Get value ```
Solution 6: Use sync/atomic for Boolean Flags
```go import "sync/atomic"
var done int32
func isDone() bool { return atomic.LoadInt32(&done) == 1 }
func setDone() { atomic.StoreInt32(&done, 1) } ```
Solution 7: Copy Values Instead of Sharing
```go func process(value int) { // Each goroutine gets its own copy localValue := value localValue++ }
func main() { for i := 0; i < 1000; i++ { go process(i) // Pass value, no sharing } } ```
Solution 8: Use WaitGroup Correctly
```go import "sync"
func main() { var wg sync.WaitGroup var mu sync.Mutex counter := 0
for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() mu.Lock() counter++ mu.Unlock() }() }
wg.Wait() fmt.Println(counter) // Always 1000 } ```
Common Patterns
Thread-Safe Singleton
```go import "sync"
type Singleton struct{}
var instance *Singleton var once sync.Once
func GetInstance() *Singleton { once.Do(func() { instance = &Singleton{} }) return instance } ```
Thread-Safe Pool
```go import "sync"
var pool = sync.Pool{ New: func() interface{} { return new(MyObject) }, }
func getObject() *MyObject { return pool.Get().(*MyObject) }
func releaseObject(obj *MyObject) { pool.Put(obj) } ```
Thread-Safe Configuration
```go type Config struct { mu sync.RWMutex data map[string]string }
func (c *Config) Get(key string) string { c.mu.RLock() defer c.mu.RUnlock() return c.data[key] }
func (c *Config) Set(key, value string) { c.mu.Lock() defer c.mu.Unlock() c.data[key] = value } ```
Best Practices
- 1.Run tests with -race flag:
go test -race ./... - 2.Don't share memory - Prefer communication via channels
- 3.Protect all shared access - Both reads and writes need synchronization
- 4.Use defer for mutex unlock - Ensures unlock even with panic
- 5.Minimize lock duration - Lock only critical sections
```go // Good: Minimal lock duration func process(c *SafeCounter) { // Do expensive work outside lock result := expensiveComputation()
c.mu.Lock() c.value = result c.mu.Unlock() } ```
Testing Tips
```bash # Run all tests with race detection go test -race ./...
# Run specific package go test -race ./pkg/sync
# Run with verbose output go test -race -v ./...
# Set timeout for race tests go test -race -timeout 30s ./... ```
Related Errors
concurrent map writes- Multiple goroutines writing to mapconcurrent map iteration and map write- Iterating while modifyingfatal error: concurrent map writes- Program crash from map race