Avoiding Data Races in Go: Best Practices for Concurrency
--
Concurrency is such a foundational capability of the Go language that it is effortless to write code that leverages multiple goroutines, sometimes without even knowing.
As an example, if you’ve ever used the net/http
package to create a web service, you’ve used goroutines.
To handle incoming HTTP traffic, the HTTP Server will generate a goroutine for every connection and one per request with HTTP/2. These goroutines are transparent to the user; you wouldn’t know they were goroutines unless you’ve read the documentation.
This ease of use makes creating multi-goroutine applications simple, it also makes it easy to create data race conditions.
Data Race
A data race is when at least two threads or, in this case, goroutines try to access the same data. For example, one goroutine tries to read data while another is writing data. This scenario will cause a Go application to panic and crash.
To explain better, let’s say we created a simple Global within our application that holds a counter.
var counter int
And an HTTP handler to increment this counter.
Because each HTTP request initiates its own goroutine, there is a high chance that two goroutines (HTTP requests) will try to increment the counter at the same time. This will result in a panic condition.
What’s unfortunate about this example is that this data race may not be found with basic testing. Data races are all about timing, two requests have to increment the counter at the same exact time.
It’s entirely possible for this example bug to get past standard testing.
Enable Data Race Detection
However, Go has a simple way to detect data race conditions. While executing tests, it is possible to turn on data race detection using the --race
flag.
$ go test --race ./...