Go代码是否存在竞态条件?为何go run -race未检测到?
为什么这段Go代码不会被检测出竞态条件?
先看这段核心代码:
func main() { m := map[string]int{ "foo": 42, "bar": 1337, } go func() { time.Sleep(1 * time.Second) tmp := map[string]int{ "foo": 44, "bar": 1339, } m = tmp }() for { val := m["foo"] fmt.Println(val) } }
关键原因:仅对map引用做原子性赋值,未触碰同一map的内部结构
Go里的map是引用类型,变量m本质是指向map底层数据结构的指针。这段代码的操作逻辑是:
- 子协程只是把
m重新赋值为指向全新map的指针,完全没有修改原map的内部键值对 - 主协程的循环只是读取
m当前指向的map里的"foo"值,同样没有修改原map
而Go竞态检测器的核心检测逻辑是是否存在对同一内存地址的并发读写:
- 原map的内存地址在主协程读取时,没有被任何协程修改(子协程只是替换了
m的指向,原map本身没被操作) - 指针大小的赋值操作在Go里是原子性的,不会出现半完成的中间状态,主协程要么读到旧map的值,要么读到新map的值,不会出现混乱
所以主、子协程并没有同时操作同一块内存,竞态检测器自然不会报错。
额外提醒
这种写法是安全的,但如果改成在子协程里直接修改原map的键值(比如m["foo"] = 44),必然会触发竞态检测报错——因为这属于对同一map底层结构的并发读写操作。
内容的提问来源于stack exchange,提问作者Evilolipop
相关产品推荐
相关产品推荐

