33 Introducing the Go Race Detector
Introducing the Go Race Detector
26 June 2013
Introduction
Race 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 works
The race detector is integrated with the go tool chain. When the -race command-line flag is set, the compiler instruments all memory accesses with code that records when and how the memory was accessed, while the runtime library watches for unsynchronized accesses to shared variables. When such "racy" behavior is detected, a warning is printed. (See this article for the details of the algorithm.)
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 detector
The race detector is fully integrated with the Go tool chain. To build your code with the race detector enabled, just add the -race flag to the command line:
$ go test -race mypkg // test the package
$ go run -race mysrc.go // compile and run the program
$ go build -race mycmd // build the command
$ go install -race mypkg // install the package
To try out the race detector for yourself, fetch and run this example program:
$ go get -race golang.org/x/blog/support/racy
$ racy
Examples
Here are two examples of real issues caught by the race detector.
Example 1: Timer.Reset
The 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 time.AfterFunc to create a Timer for the first message and then uses the Reset method to schedule the next message, re-using the Timer each time.
11 func main() {
12 start := time.Now()
13 var t *time.Timer
14 t = time.AfterFunc(randomDuration(), func() {
15 fmt.Println(time.Now().Sub(start))
16 t.Reset(randomDuration())
17 })
18 time.Sleep(5 * time.Second)
19 }
20
21 func randomDuration() time.Duration {
22 return time.Duration(rand.Int63n(1e9))
23 }
This looks like reasonable code, but under certain circumstances it fails in a surprising way:
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x8 pc=0x41e38a] goroutine 4 [running]:
time.stopTimer(0x8, 0x12fe6b35d9472d96)
src/pkg/runtime/ztime_linux_amd64.c:35 +0x25
time.(*Timer).Reset(0x0, 0x4e5904f, 0x1)
src/pkg/time/sleep.go:81 +0x42
main.func·001()
race.go:14 +0xe3
created by time.goFunc
src/pkg/time/sleep.go:122 +0x48
What's going on here? Running the program with the race detector enabled is more illuminating:
==================
WARNING: DATA RACE
Read by goroutine 5:
main.func·001()
race.go:14 +0x169 Previous write by goroutine 1:
main.main()
race.go:15 +0x174 Goroutine 5 (running) created at:
time.goFunc()
src/pkg/time/sleep.go:122 +0x56
timerproc()
src/pkg/runtime/ztime_linux_amd64.c:181 +0x189
==================
The race detector shows the problem: an unsynchronized read and write of the variable t from different goroutines. If the initial timer duration is very small, the timer function may fire before the main goroutine has assigned a value to t and so the call to t.Reset is made with a nil t.
To fix the race condition we change the code to read and write the variable t only from the main goroutine:
11 func main() {
12 start := time.Now()
13 reset := make(chan bool)
14 var t *time.Timer
15 t = time.AfterFunc(randomDuration(), func() {
16 fmt.Println(time.Now().Sub(start))
17 reset <- true
18 })
19 for time.Since(start) < 5*time.Second {
20 <-reset
21 t.Reset(randomDuration())
22 }
23 }
Here the main goroutine is wholly responsible for setting and resetting the Timer t and a new reset channel communicates the need to reset the timer in a thread-safe way.
A simpler but less efficient approach is to avoid reusing timers.
Example 2: ioutil.Discard
The second example is more subtle.
The ioutil package's Discard object implements io.Writer, but discards all the data written to it. Think of it like /dev/null: a place to send data that you need to read but don't want to store. It is commonly used with io.Copy to drain a reader, like this:
io.Copy(ioutil.Discard, reader)
Back in July 2011 the Go team noticed that using Discard in this way was inefficient: the Copy function allocates an internal 32 kB buffer each time it is called, but when used with Discard the buffer is unnecessary since we're just throwing the read data away. We thought that this idiomatic use of Copy and Discard should not be so costly.
The fix was simple. If the given Writer implements a ReadFrom method, a Copy call like this:
io.Copy(writer, reader)
is delegated to this potentially more efficient call:
writer.ReadFrom(reader)
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 ioutil.Discard.
Here is the known-racy code in io/ioutil, where Discard is a devNull that shares a single buffer between all of its users.
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 trackDigestReader type, which wraps an io.Reader and records the hash digest of what it reads.
type trackDigestReader struct {
r io.Reader
h hash.Hash
}
func (t trackDigestReader) Read(p []byte) (n int, err error) {
n, err = t.r.Read(p)
t.h.Write(p[:n])
return
}
For example, it could be used to compute the SHA-1 hash of a file while reading it:
tdr := trackDigestReader{r: file, h: sha1.New()}
io.Copy(writer, tdr)
fmt.Printf("File hash: %x", tdr.h.Sum(nil))
In some cases there would be nowhere to write the data—but still a need to hash the file—and so Discard would be used:
io.Copy(ioutil.Discard, tdr)
But in this case the blackHole buffer isn't just a black hole; it is a legitimate place to store the data between reading it from the source io.Reader and writing it to the hash.Hash. With multiple goroutines hashing files simultaneously, each sharing the same blackHole buffer, the race condition manifested itself by corrupting the data between reading and hashing. No errors or panics occurred, but the hashes were wrong. Nasty!
func (t trackDigestReader) Read(p []byte) (n int, err error) {
// the buffer p is blackHole
n, err = t.r.Read(p)
// p may be corrupted by another goroutine here,
// between the Read above and the Write below
t.h.Write(p[:n])
return
}
The bug was finally fixed by giving a unique buffer to each use of ioutil.Discard, eliminating the race condition on the shared buffer.
Conclusions
The 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 "go test -race" on your code today!
By Dmitry Vyukov and Andrew Gerrand
Related articles
- HTTP/2 Server Push
- Introducing HTTP Tracing
- Generating code
- Go Concurrency Patterns: Context
- Go Concurrency Patterns: Pipelines and cancellation
- Advanced Go Concurrency Patterns
- Go maps in action
- go fmt your code
- Concurrency is not parallelism
- Organizing Go code
- Go videos from Google I/O 2012
- Debugging Go programs with the GNU Debugger
- The Go image/draw package
- The Go image package
- The Laws of Reflection
- Error handling and Go
- "First Class Functions in Go"
- Profiling Go Programs
- A GIF decoder: an exercise in Go interfaces
- Introducing Gofix
- Godoc: documenting Go code
- Gobs of data
- C? Go? Cgo!
- JSON and Go
- Go Slices: usage and internals
- Go Concurrency Patterns: Timing out, moving on
- Defer, Panic, and Recover
- Share Memory By Communicating
- JSON-RPC: a tale of interfaces
33 Introducing the Go Race Detector的更多相关文章
- 28 Data Race Detector 数据种类探测器:数据种类探测器手册
Data Race Detector 数据种类探测器:数据种类探测器手册 Introduction Usage Report Format Options Excluding Tests How To ...
- golang中的race检测
golang中的race检测 由于golang中的go是非常方便的,加上函数又非常容易隐藏go. 所以很多时候,当我们写出一个程序的时候,我们并不知道这个程序在并发情况下会不会出现什么问题. 所以在本 ...
- 31 Godoc: documenting Go code 编写良好的文档关于godoc
Godoc: documenting Go code 编写良好的文档关于godoc 31 March 2011 The Go project takes documentation seriousl ...
- 32 Profiling Go Programs 分析go语言项目
Profiling Go Programs 分析go语言项目 24 June 2011 At Scala Days 2011, Robert Hundt presented a paper titl ...
- 30 C? Go? Cgo!
C? Go? Cgo! 17 March 2011 Introduction Cgo lets Go packages call C code. Given a Go source file writ ...
- 25 The Go image/draw package go图片/描绘包:图片/描绘包的基本原理
The Go image/draw package go图片/描绘包:图片/描绘包的基本原理 29 September 2011 Introduction Package image/draw de ...
- 24 The Go image package go图片包:图片包的基本原理
The Go image package go图片包:图片包的基本原理 21 September 2011 Introduction The image and image/color packag ...
- 23 The Laws of Reflection 反射定律:反射包的基本原理
The Laws of Reflection 反射定律:反射包的基本原理 6 September 2011 Introduction 介绍 Reflection in computing is th ...
- 22 Gobs of data 设计和使用采集数据的包
Gobs of data 24 March 2011 Introduction To transmit a data structure across a network or to store it ...
随机推荐
- Linux内核分析实验二:mykernel实验指导(操作系统是如何工作的)
计算机是如何工作的?(总结)——三个法宝 存储程序计算机工作模型,计算机系统最最基础性的逻辑结构: 函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆栈机制对于计算机来说并不那么重要 ...
- 单点登录(四)-----遇到问题-----cas server 源码部署tomcat运行报错ClassNotFoundException: org.jasig.cas.CasEnvironmentCo
情况 cas单点登录 cas server 源码部署tomcat运行报错 把cas server的代码下载下来后使用gradle插件或者maven插件以及转化成eclipse·后导入发现部署到tomc ...
- Unhandled rejection RangeError: Maximum call stack size exceededill install loadIdealTree
npm安装时候报这个错误,解决方法如下: 删除项目中的 package-lock.json 文件 和 node_modules 文件夹,然后再尝试 npm install 详细参考
- 2018 “百度之星”程序设计大赛 - 初赛(A)度度熊学队列 list rope
c++ list使用 #include <cstdio> #include <cstdlib> #include <cmath> #include <cstr ...
- sql 事务的四种隔离级别
在 SQL 标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的.较低级别的隔离通常可以执行更高的并发,系统的开销也更低. read unco ...
- 什么是Docker并且它为什么这么受欢迎
什么是Docker (why it's so hot than hot) Docker是一个使用容器来方便快捷的创建,部署,运行程序的工具,容器允许开发人员将应用程序的一切打包(镜像),例如库和其他的 ...
- Go_20: Golang 中 time 包的使用
time包中包括两类时间:时间点(某一时刻)和时常(某一段时间) 1. 时间常量(时间格式化) const ( ANSIC = "Mon Jan _2 15:04:05 2006" ...
- js判断当前浏览器是pc端还是移动端
根据用户的访问设备的不同来显示不同的页面样式,主要是判断移动设备还是电脑浏览器访问的. 下面给出js判断处理代码,以作参考. <script type="text/javascript ...
- MySQL报错】ERROR 1558 (HY000): Column count of mysql.user is wrong. Expected 43, found 39.
之前在centos6.4系统安装的是自带的mysql 5.1版本,后来升级到了5.6版本,执行以下命令报错 在网上查找原因说说因为升级不当导致,执行以下命令即可正常执行命令 mysql_upgrade ...
- mysql 主从配置(master/slave)
1. 在每台服务器上创建复制账号(也可以只在master上创建用户,这里配置两个是为了方便以后切换) 备库运行的I/O县城需要建立一个到主库的TCP/IP连接,所以必须在主库创建一个用户,并赋予合适 ...