已加锁的Go代码为何仍被go run -race检测出DATA RACE?
我觉得下面的代码不该出现数据竞态,但日志里明确报了DATA RACE,试了用defer解锁也没解决,到底哪里漏了?
代码片段(some_file.go)
type Key struct { } type Val struct { } type Tmp struct { smMap map[Key]Val lock sync.RWMutex } func (t *Tmp) setMap(k Key, v Val) { t.lock.Lock() t.smMap[k] = v // line#13 t.lock.Unlock() } func (t *Tmp) deleteFromMap(k Key) { t.lock.Lock() delete(t.smMap, k) // line#19 t.lock.Unlock() } func (t *Tmp) getMap(k Key) Val { t.lock.Lock() defer t.lock.Unlock() return t.smMap[k] }
竞态检测日志
WARNING: DATA RACE
Write at 0x00c0004cc990 by main goroutine:
runtime.mapassign()
/usr/local/go/src/runtime/map.go:578 +0x0
some_file.go:13 +0x70Previous write at 0x00c0004cc990 by goroutine 925:
runtime.mapdelete()
/usr/local/go/src/runtime/map.go:695 +0x0
some_file.go:19Goroutine 925 (running) created at:
问题排查方向
存在未通过方法直接操作smMap的代码
如果你在业务逻辑的其他地方,没有调用setMap/deleteFromMap/getMap,而是直接对smMap执行读写(比如tmp.smMap[key] = val或delete(tmp.smMap, key)),这类操作不会持有锁,必然引发竞态。Tmp结构体被意外复制
sync.RWMutex是值类型,绝对不能复制。如果代码中出现值传递Tmp实例的场景(比如把Tmp作为参数传给函数、赋值给其他变量),锁会被复制成多个独立实例,无法起到跨goroutine的互斥作用,等于没锁保护map的并发读写。锁的作用范围未覆盖完整操作
检查setMap/deleteFromMap方法中,锁是否包裹了所有对smMap的操作。你当前的代码看起来没问题,但如果后续修改时在锁外新增了对smMap的操作,也会触发竞态。
另外补充:getMap是读操作,应该使用t.lock.RLock()和t.lock.RUnlock()(配合defer),这样允许多个读操作同时进行,提升并发性能,但这不是当前竞态的触发原因。
内容的提问来源于stack exchange,提问作者nimpostor

