# 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. 1.Run tests with -race flag: go test -race ./...
  2. 2.Don't share memory - Prefer communication via channels
  3. 3.Protect all shared access - Both reads and writes need synchronization
  4. 4.Use defer for mutex unlock - Ensures unlock even with panic
  5. 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 ./... ```

  • concurrent map writes - Multiple goroutines writing to map
  • concurrent map iteration and map write - Iterating while modifying
  • fatal error: concurrent map writes - Program crash from map race