Published on

Race Condition

Authors

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:

main.go

_12
package main
_12
_12
var counter int
_12
_12
func incrementCounter() {
_12
counter++
_12
}
_12
_12
func main() {
_12
go incrementCounter()
_12
go incrementCounter()
_12
}

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


_21
❯ go run --race main.go
_21
==================
_21
WARNING: DATA RACE
_21
Read at 0x000000548700 by goroutine 7:
_21
main.incrementCounter()
_21
/home/upix/code/oss/golang-playground/main.go:6 +0x29
_21
_21
Previous write at 0x000000548700 by goroutine 6:
_21
main.incrementCounter()
_21
/home/upix/code/oss/golang-playground/main.go:6 +0x44
_21
_21
Goroutine 7 (running) created at:
_21
main.main()
_21
/home/upix/code/oss/golang-playground/main.go:11 +0x35
_21
_21
Goroutine 6 (finished) created at:
_21
main.main()
_21
/home/upix/code/oss/golang-playground/main.go:10 +0x29
_21
==================
_21
Found 1 data race(s)
_21
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:


_15
package main
_15
_15
var counter int
_15
var mu sync.Mutex
_15
_15
func incrementCounter() {
_15
mu.Lock()
_15
defer mu.Unlock()
_15
counter++
_15
}
_15
_15
func main() {
_15
go incrementCounter()
_15
go incrementCounter()
_15
}

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:


_14
package main
_14
_14
import "sync/atomic"
_14
_14
var counter int32
_14
_14
func incrementCounter() {
_14
atomic.AddInt32(&counter, 1)
_14
}
_14
_14
func main() {
_14
go incrementCounter()
_14
go incrementCounter()
_14
}