Suppose you want to add an http handler which can show some things that in memory.
Simple, right? Actually, if you aren’t careful, doing this can lead to data races, even though only one goroutine is writing the data at any given time.
Here’s the golang code (also on github) that reproduces the issue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
if you run this code with go run main.go
and then open http://localhost:8080
in your browser, it will work as expected.
But not so fast! Lurking in this code are data races, and if you run this with race detection enabled using go run -race main.go
and then access it with the browser, it will panic with:
1 2 3 4 5 6 7 8 9 10 |
|
because there are two goroutines accessing the same slice without any protection — the main goroutine running the http server, and the goroutine running updateFooSlice
.
Fix #1 – use sync.mutex to lock the slice
This isn’t necessarily the best way to fix this, but it’s the simplest to understand and explain.
Here are the changes to the code (also on github):
- Import the
sync
package - Create a sync.Mutex object in the package-global namespace
- Before updating the slice, lock the mutex, and after updating it, unlock it.
- Before the http handler access the slice, it locks the mutex, and after it’s done, it unlocks it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
If you now re-run the code with the -race
flag and access http://localhost:8080
, it won’t panic.
Digression – chan chan
Before we can talk about Fix #2, we need to take a digression to talk about chan chan’s — channels which contain other channels as the morsels of data that pass through the channel tubes (metachannels, if you will).
tl;dr A channel describes a transport of sorts. You can send a thing down that transport. When using a chan chan, the thing you want to send down the transport is another transport to send things back.
If it’s still a little fuzzy for you, here’s the long description of chan chan’s with diagrams
Fix #2 – Use channels instead of sync.Mutex
In this version of the fix, the goroutine running the http handler (the main goroutine in this case), makes a response chan and passes it into a request chan chan that both goroutines can access. It then reads the response from the response chan, which will contain a copy of the FooSlice.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The other goroutine updates the FooSlice and is also checking the request chan chan for new messages. If it gets a new request message, it makes a copy of the FooSlice and sends it to the response chan.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Again, if you now re-run this code with the -race
flag and access http://localhost:8080
, it won’t panic.
The full code sample is available on gihub