Data Races
When writing concurrent applications, it is common to run into what is called arace condition.15A race condition occurs when two different goroutines try to access the same shared resource.
Consider Listing 13.25. There are two different goroutines. One goroutine inserts values into a map, and the other goroutine ranges over the map and prints the values.
Listing 13.25 Two Goroutines Accessing a Shared Map
// launch a goroutine to // write data in the mapgo func() {fori:=0;i<10;i++ {// loop putting data in the mapdata[i] =true}// cancel the context取消() }()// launch a goroutine to // read data from the mapgo func() {// loop through the map // and print the keys/valuesfork,v:=rangedata{fmt.Printf("%d: %v\n",k,v) } }()
In Listing 13.26, we use those two goroutines to write a test to assert the map is written to and read from correctly.
Listing 13.26 A Passing Testing Without the Race Detector
funcTest_Mutex(t*testing.T) {t.Parallel()// create a new cancellable context // to stop the test when the goroutines // are finishedctx:=context.Background()ctx,取消:=context.WithTimeout(ctx,20*time.Millisecond)defer取消()// create a map to be used // as a shared resourcedata:=map[int]bool{}// launch a goroutine to // write data in the mapgo func() {fori:=0;i<10;i++ {// loop putting data in the mapdata[i] =true}// cancel the context取消() }()// launch a goroutine to // read data from the mapgo func() {// loop through the map // and print the keys/valuesfork,v:=rangedata{fmt.Printf("%d: %v\n",k,v) } }()// wait for the context to be canceled<-ctx.Done()iflen(data) !=10{t.Fatalf("expected 10 items in the map, got %d",len(data)) } }
$ go test -v === RUN Test_Mutex === PAUSE Test_Mutex === CONT Test_Mutex --- PASS: Test_Mutex (0.00s) PASS ok demo 0.471s
Go Version: go1.19
A quick glance at the test output, Listing 13.26, would seem to imply that the tests have passed successfully, but this is not the case.
The Race Detector
几啊f the Go commands, such astestandbuild, have the-raceflag exposed. When used, the-raceflag tells the Go compiler to create a special version of the binary or test binary that will detect and report race conditions.
If we run the test again, this time with the-raceflag, we get averydifferent result, as shown in Listing 13.27.
Listing 13.27 Tests Failing with the Race Detector
$ go test -v -race === RUN Test_Mutex === PAUSE Test_Mutex === CONT Test_Mutex --- PASS: Test_Mutex (0.00s) ================== WARNING: DATA RACE Read at 0x00c00011c3f0 by goroutine 9: runtime.mapdelete() /usr/local/go/src/runtime/map.go:695 +0x46c demo.Test_Mutex.func2() ./demo_test.go:46 +0x50 Previous write at 0x00c00011c3f0 by goroutine 8: runtime.mapaccess2_fast64() /usr/local/go/src/runtime/map_fast64.go:53 +0x1cc demo.Test_Mutex.func1() ./demo_test.go:32 +0x50 Goroutine 9 (running) created at: demo.Test_Mutex() ./demo_test.go:43 +0x188 testing.tRunner() /usr/local/go/src/testing/testing.go:1439 +0x18c testing.(*T).Run.func1() /usr/local/go/src/testing/testing.go:1486 +0x44 Goroutine 8 (finished) created at: demo.Test_Mutex() ./demo_test.go:28 +0x124 testing.tRunner() /usr/local/go/src/testing/testing.go:1439 +0x18c testing.(*T).Run.func1() /usr/local/go/src/testing/testing.go:1486 +0x44 ================== FAIL exit status 1 FAIL demo 0.962s
Go Version: go1.19
As you can see from the output, the Go race detector found a race condition in our code.
If we examine the top two entries in a race condition warning, Listing 13.28, it tells us where the two conflicting lines of code are.
Listing 13.28 Reading the Race Detector Output
Read at 0x00c00018204b by goroutine 9: demo.Test_Mutex.func2() problem/demo_test.go:46 +0xa5 Previous write at 0x00c00018204b by goroutine 8: demo.Test_Mutex.func1() problem/demo_test.go:32 +0x5c
A read of the shared resource was happening atdemo_test.go:46, and a write was happening atdemo_test.go:32. We need to synchronize or lock these two goroutines so that they don’t both try to access the shared resource at the same time.
Most, but Not All
The Go race detector makes a simple guarantee with you (the end user).
A race conditionwillpanic and crash your application. If the race detector finds a race condition, youmustfix it.
Wrapping Up the Race Detector
The race detector is aninvaluabletool when developing Go applications. When running tests with the-raceflag, you will notice a slowdown in test performance. The race detector has to do a lot of work to track those conditions.
Once identified, thesyncpackage, Listing 13.29, provides a number of ways that you can fix issues.
Listing 13.29 ThesyncPackage
$ go doc -short sync type Cond struct{ ... } func NewCond(l Locker) *Cond type Locker interface{ ... } type Map struct{ ... } type Mutex struct{ ... } type Once struct{ ... } type Pool struct{ ... } type RWMutex struct{ ... } type WaitGroup struct{ ... }
Go Version: go1.19