GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing go.dev — Introducing the Go Race Detector - The Go Programming Language The Go BlogIntroducing the Go Race Detector
Dmitry Vyukov and Andrew Gerrand IntroductionRace conditions are among the most insidious and elusive programming errors. They typically cause erratic and mysterious failures, often long after the code has been deployed to production. While Go’s concurrency mechanisms make it easy to write clean concurrent code, they don’t prevent race conditions. Care, diligence, and testing are required. And tools can help. We’re happy to announce that Go 1.1 includes a race detector, a new tool for finding race conditions in Go code. It is currently available for Linux, OS X, and Windows systems with 64-bit x86 processors. The race detector is based on the C/C++ ThreadSanitizer runtime library, which has been used to detect many errors in Google’s internal code base and in Chromium. The technology was integrated with Go in September 2012; since then it has detected 42 races in the standard library. It is now part of our continuous build process, where it continues to catch race conditions as they arise. How it worksThe race detector is integrated with the go tool chain. When the
Because of its design, the race detector can detect race conditions only when they are actually triggered by running code, which means it’s important to run race-enabled binaries under realistic workloads. However, race-enabled binaries can use ten times the CPU and memory, so it is impractical to enable the race detector all the time. One way out of this dilemma is to run some tests with the race detector enabled. Load tests and integration tests are good candidates, since they tend to exercise concurrent parts of the code. Another approach using production workloads is to deploy a single race-enabled instance within a pool of running servers. Using the race detectorThe race detector is fully integrated with the Go tool chain.
To build your code with the race detector enabled, just add the
To try out the race detector for yourself, copy this example program into
Then run it with the race detector enabled:
ExamplesHere are two examples of real issues caught by the race detector. Example 1: Timer.ResetThe first example is a simplified version of an actual bug found by the race
detector. It uses a timer to print a message after a random duration between 0
and 1 second. It does so repeatedly for five seconds.
It uses
package main
import (
"fmt"
"math/rand"
"time"
)
10 func main() { 11 start := time.Now() 12 var t *time.Timer 13 t = time.AfterFunc(randomDuration(), func() { 14 fmt.Println(time.Now().Sub(start)) 15 t.Reset(randomDuration()) 16 }) 17 time.Sleep(5 * time.Second) 18 } 19 20 func randomDuration() time.Duration { 21 return time.Duration(rand.Int63n(1e9)) 22 } 23 This looks like reasonable code, but under certain circumstances it fails in a surprising way:
What’s going on here? Running the program with the race detector enabled is more illuminating:
The race detector shows the problem: an unsynchronized read and write of the
variable To fix the race condition we change the code to read and write the variable
package main
import (
"fmt"
"math/rand"
"time"
)
10 func main() { 11 start := time.Now() 12 reset := make(chan bool) 13 var t *time.Timer 14 t = time.AfterFunc(randomDuration(), func() { 15 fmt.Println(time.Now().Sub(start)) 16 reset <- true 17 }) 18 for time.Since(start) < 5*time.Second { 19 <-reset 20 t.Reset(randomDuration()) 21 } 22 } 23
func randomDuration() time.Duration {
return time.Duration(rand.Int63n(1e9))
}
Here the main goroutine is wholly responsible for setting and resetting the
A simpler but less efficient approach is to avoid reusing timers. Example 2: ioutil.DiscardThe second example is more subtle. The
Back in July 2011 the Go team noticed that using The fix was simple.
If the given
is delegated to this potentially more efficient call:
We added a ReadFrom method to Discard’s underlying type, which has an internal buffer that is shared between all its users. We knew this was theoretically a race condition, but since all writes to the buffer should be thrown away we didn’t think it was important. When the race detector was implemented it immediately flagged this code as racy. Again, we considered that the code might be problematic, but decided that the race condition wasn’t “real”. To avoid the “false positive” in our build we implemented a non-racy version that is enabled only when the race detector is running. But a few months later Brad encountered a
frustrating and strange bug.
After a few days of debugging, he narrowed it down to a real race condition
caused by Here is the known-racy code in var blackHole [4096]byte // shared buffer
func (devNull) ReadFrom(r io.Reader) (n int64, err error) {
readSize := 0
for {
readSize, err = r.Read(blackHole[:])
n += int64(readSize)
if err != nil {
if err == io.EOF {
return n, nil
}
return
}
}
}
Brad’s program includes a
For example, it could be used to compute the SHA-1 hash of a file while reading it:
In some cases there would be nowhere to write the data—but still a need to hash
the file—and so
But in this case the
The bug was finally
fixed
by giving a unique buffer to each use of ConclusionsThe race detector is a powerful tool for checking the correctness of concurrent programs. It will not issue false positives, so take its warnings seriously. But it is only as good as your tests; you must make sure they thoroughly exercise the concurrent properties of your code so that the race detector can do its job. What are you waiting for? Run
Next article: The first Go program |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |