2

为什么你不应该接受有 race 的代码

 1 year ago
source link: https://xargin.com/why-you-should-reject-racy-code/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

为什么你不应该接受有 race 的代码

Dec 25, 2021 3 min read

在任何语言的并发编程场景中,都有 race 问题,现代语言为了解决 race 问题有两种思路,一种是像 rust 那样的通过所有权+Sync/Send 限制用户尽量无法写出带 race 的代码;一种是像 Go 这样,通过 race detector 在测试期间检查数据竞争问题。

Go 的 race detector 设计决定了其无法在线上环境开启,而很多公司的项目上线前其实是没有 race test 的环节的,这就导致了一些 Gopher 认为我写出 race 的代码也没关系,因为可以“最终一致”。

在 Go 官方的 《The Go Memory Model》 一文中已经驳斥过这些观点了,比如有这么一个例子:

var a string
var done bool

func setup() {
	a = "hello, world"
	done = true
}

func main() {
	go setup()
	for !done {
	}
	print(a)
}

全局变量 a 的写入和 done 的修改从代码层面讲存在先后关系,但因为你没有在代码中使用任何同步工具(哪怕是 atomic 操作),所以这样的代码你没法保证在 for 循环检查到 done 变成 true 之后,一定能打印出 "hello, world"。

这里还只是因为 CPU 和编译器的乱序执行导致了问题,按照这些程序员的想法,我不关心顺序,我只关注最终一致,反正 done 肯定能被改成 true,a 也一定能被修改成 hello, world。

这种想法也是有问题的,多核环境下 CPU 有多级缓存,如果连 atomic 都不使用,那么在多个核心间也不一定会同步你这种写行为。最差的情况下,在一个核心里写了 done = true,另一个核心一直读不到,这也是正常的(并不好复现)。说不定什么时候硬件为了优化就真的会给你这么干啊。

官方给出的这个例子,我们在 build/run 的时候加上简单的 -race flag,也是可以及时发现问题的:

~/test git:master ❯❯❯ go run -race ./r.go
==================
WARNING: DATA RACE
Write at 0x0000011506a1 by goroutine 6:
  main.setup()
      /Users/xargin/test/r.go:8 +0x73

Previous read at 0x0000011506a1 by main goroutine:
  main.main()
      /Users/xargin/test/r.go:13 +0x3e

Goroutine 6 (running) created at:
  main.main()
      /Users/xargin/test/r.go:12 +0x32
==================
==================
WARNING: DATA RACE
Read at 0x000001121bf0 by main goroutine:
  main.main()
      /Users/xargin/test/r.go:15 +0x53

Previous write at 0x000001121bf0 by goroutine 6:
  main.setup()
      /Users/xargin/test/r.go:7 +0x30

Goroutine 6 (finished) created at:
  main.main()
      /Users/xargin/test/r.go:12 +0x32
==================
hello, worldFound 2 data race(s)
exit status 66

发现了有 data race 就一定要去解决,如果允许这样的代码进入到你的工程里,那么这样的错误就会越来越多,当未来某个时刻你需要去查并发导致的 bug 了,那成百上千的 race 输出都是技术债,到时候再来还就晚了。

如果可以的话,race test 也最好集成在你的 CI 环境中,初级工程师最擅长的就是这个:

image-38.png

如果你允许有 race 的代码进入主分支,日积月累会有更多的 race 通过初级程序员的复制粘贴扩散出去。

当你花了一个星期还没有办法定位出线上偶发的并发问题的时候,可能就只能提桶跑路了。

要写并发相关的代码,还是要好好学习一下并发知识的。这里可以推荐两本相关的书:《Shared Memory Synchronization》 和 《perfbook》。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK