Race Condition

1 min read Tweet this post

A data race in Go (or any other programming language) occurs when two or more goroutines access the same shared data concurrently and at least one of those accesses is a write operation. This can lead to unpredictable behavior and can cause bugs in your code.

In Go, data races can occur when multiple goroutines access the same variables or memory locations without proper synchronization. For example, consider the following code:

package main

var counter int

func incrementCounter() {
    counter++
}

func main() {
    go incrementCounter()
    go incrementCounter()
}

In this code, two goroutines are incrementing the same counter variable concurrently. This can lead to a data race because both goroutines are writing to the same memory location without any synchronization mechanism. How to find out it race condition occurred ? Try run with flag --race like example below

 go run --race main.go
==================
WARNING: DATA RACE
Read at 0x000000548700 by goroutine 7:
  main.incrementCounter()
      /home/upix/code/oss/golang-playground/main.go:6 +0x29

Previous write at 0x000000548700 by goroutine 6:
  main.incrementCounter()
      /home/upix/code/oss/golang-playground/main.go:6 +0x44

Goroutine 7 (running) created at:
  main.main()
      /home/upix/code/oss/golang-playground/main.go:11 +0x35

Goroutine 6 (finished) created at:
  main.main()
      /home/upix/code/oss/golang-playground/main.go:10 +0x29
==================
Found 1 data race(s)
exit status 66

To avoid data races in Go, you need to use synchronization primitives such as channels, mutexes, or atomic operations. For example, you can use a mutex to ensure that only one goroutine at a time can access the shared data:

package main

var counter int
var mu sync.Mutex

func incrementCounter() {
    mu.Lock()
    defer mu.Unlock()
    counter++
}

func main() {
    go incrementCounter()
    go incrementCounter()
}

With this implementation, the mutex ensures that only one goroutine can access the counter variable at a time, avoiding any data races.

The other alternative is use the atomic package in Go to perform atomic operations on shared variables, ensuring that they are executed as a single indivisible operation. For example:

package main

import "sync/atomic"

var counter int32

func incrementCounter() {
    atomic.AddInt32(&counter, 1)
}

func main() {
    go incrementCounter()
    go incrementCounter()
}
go race-condition