jasper的技术小窝

关注DevOps、运维监控、Python、Golang、开源、大数据、web开发、互联网

Golang中用race检测并发

作者:jasper | 分类:Golang | 标签:   | 阅读 353 次 | 发布:2017-08-05 6:38 p.m.

数据竞争是并发系统中最常见和最难调试类型的错误之一。特别是在Golang中,由于goroutine的使用,这样的问题更容易出现,好在Golang提供了race这个功能。

数据竞争发生的条件

当两个goroutine同时访问相同的变量并且访问中的至少一个是写入时,发生数据竞争。有关详细信息,请参阅Golang内存模型。来个最简单的例子吧:

func main() {
    a := 1
    go func(){
        a = 2
    }()
    fmt.Println("a is ", a)

    time.Sleep(2 * time.Second)
}

这里main goroutine和一个goroutine中都对a有访问,并且有对a的写操作,所以会触发race。

race的使用

在golang中自带的race的检测,使用 -race 标志在command中来实现:

$ go test -race mypkg    
$ go run -race mysrc.go 
$ go build -race mycmd   
$ go install -race mypkg 

其中还可以用环境变量GORACE来跟一些参数,格式为:

GORACE="option1=val1 option2=val2"

可选的参数有:

  • log_path:默认stderr,race的结果写入的路径;
  • exitcode:默认66,在检测到race后退出时的状态码;
  • strip_path_prefix:默认空字符串,race结果中去掉文件夹前缀,这样更简洁;
  • history_size:默认为1,每个goroutine内存访问历史记录是32K * 2 ** history_size元素。增加此值可以避免报告中的“failed to restore the stack”错误,代价是增加内存使用量。
  • halt_on_error: 默认0,控制是否程序在触发了第一次data race就退出。

例子:

$ GORACE="log_path=/tmp/race/report strip_path_prefix=/my/go/sources/" go test -race

下面我们来对上面的小段程序来使用race检测下,结果如下:

==================
a is  1
WARNING: DATA RACE
Write at 0x00c420076178 by goroutine 6:
  main.main.func1()
      race.go:12 +0x3b

Previous read at 0x00c420076178 by main goroutine:
  main.main()
      race.go:17 +0xb4

Goroutine 6 (running) created at:
  main.main()
      race.go:14 +0x8e
==================
Found 1 data race(s)
exit status 66

strip_path_prefix去掉了文件前缀,可以看到报告了一处data race,在race.go:12处有goroutine 60x00c420076178的写入,在race.go:17处有main goroutine对同一变量0x00c420076178的读取操作。退出status为66。

race的原理

race的底层实现基于ThreadSanitizerAlgorithm算法,该算法的大体为,随着线程的时钟递增,创建与当前内存访问相对应的新的Shadow Word。然后状态机迭代存储在Shadow State中的所有Shadow Word。如果其中一个老的Shadow Word与新Shadow Word组成race,则data race被检测出,具体可参见ThreadSanitizerAlgorithm

解决办法

遇到race后的解决办法一般来说有三种:channels,加锁(mutexes)和原子(atomic counters)。其实在上一篇Golang内存模型中也有提及。例如对于上面的例子,我们这样改下:

func main() {
    a := 1
    c := make(chan int, 1)
    go func(){
        a = 2
        c<-1
    }()
    <-c
    fmt.Println("a is ", a)

    time.Sleep(2 * time.Second)
}

使用channel来使变量的对两个goroutine都可见,再次运行race后,data race的结果就消失啦。

结论

提醒一下,虽然race功能可用来检测并发情景下的data race。但是强烈不建议在生产环境里使用,因为这对性能的影响超级大。赶紧检测一下你的代码吧,找出这些data race,不再让你的应用莫名奇妙的hang住啦。


转载请注明出处http://www.opscoder.info/golang_data_race

其他分类: