英文:
False-positive in the Golang Race Detector? [repost]
问题
我几天前发布了这个问题,但由于代码中有一个错误,所以被关闭了。我已经修复了这个问题,所以重新发布这个问题。
package main
import (
"fmt"
"time"
"sync/atomic"
"math/rand"
)
//这些数据通常通过HTTP请求获取
var dummyData1 = []string{"a", "b", "c", "d", "e", "f"}
var activeMap = new(int32)
var map1 = make(map[string]*int32)
var map2 map[string]*int32
var combinedMap = make(map[string]*int32)
func mapKeyUpdater () {
for _, key := range dummyData1 {
combinedMap[key] = new(int32)
map1[key] = new(int32)
}
atomic.AddInt32(activeMap, 1)
time.Sleep(3 * time.Second)
for {
if atomic.LoadInt32(activeMap) == 1 {
map2 = make(map[string]*int32)
for _, key := range dummyData1 {
map2[key] = new(int32)
}
atomic.AddInt32(activeMap, 1)
time.Sleep(500 * time.Millisecond) //在下面的编辑后添加
for key, count := range map1{
*combinedMap[key] += *count
}
} else {
map1 = make(map[string]*int32)
for _, key := range dummyData1 {
map1[key] = new(int32)
}
atomic.AddInt32(activeMap, -1)
time.Sleep(500 * time.Millisecond) //在下面的编辑后添加
for key, count := range map2 {
*combinedMap[key] += *count
}
}
time.Sleep(3 * time.Second)
}
}
func counter () {
for {
randomIndex := rand.Intn(5)
randomKey := dummyData1[randomIndex]
if atomic.LoadInt32(activeMap) == 1 {
val := atomic.AddInt32(map1[randomKey], 100)
fmt.Printf("在Map1中给%v添加了100。更新后的值为%v\n", randomKey, val)
} else {
val := atomic.AddInt32(map2[randomKey], 100)
fmt.Printf("在Map2中给%v添加了100。更新后的值为%v\n", randomKey, val)
}
}
}
func main () {
go mapKeyUpdater()
time.Sleep(500 * time.Millisecond)
go counter()
time.Sleep(15 * time.Second)
}
现在,当我用命令go run -race raceBug.go
运行时,每次都会出现4个数据竞争。然而,从输出结果可以明显看出,没有数据竞争,而且地图的工作正常。
这篇由Google工程师撰写的文章说-https://medium.com/@val_deleplace/does-the-race-detector-catch-all-data-races-1afed51d57fb
> 如果你坚信你目睹了一个误报,那么请为竞争检测器报告一个错误。如果你有充分的理由相信竞争条件是由标准库或运行时引起的(而不是你自己的代码),那么请为标准库或运行时报告一个错误。
由于我对Go还不太熟悉,只是想确认一下这一点。
编辑:为了确保combinedMap
循环在开始之前有足够的时间,我添加了一个time.Sleep(500 * time.Millisecond)
。然而,仍然检测到了数据竞争,但输出结果现在不同了。
新的输出结果
==================
警告:数据竞争在Map2中给e添加了100。更新后的值为9447300
在0x0000011cdbd0处由goroutine 7写入:
在Map2中给a添加了100。更新后的值为9465100
main.mapKeyUpdater()
/raceBug2.go:35 +0x14d
在0x0000011cdbd0之前由goroutine 9读取:
在Map2中给b添加了100。更新后的值为9461300
[无法恢复堆栈]
Goroutine 7(正在运行)创建于:
main.main()
/raceBug2.go:64 +0x29
在Map2中给d添加了100。更新后的值为9479400
Goroutine 9(正在运行)创建于:
main.main()
/raceBug2.go:66 +0x44
在Map2中给c添加了100。更新后的值为9465200
==================
英文:
I had posted this question a few days ago and it got closed as there was an error in the code. Got that fixed up and so re-posting this
package main
import (
"fmt"
"time"
"sync/atomic"
"math/rand"
)
//This data is normally fetched via HTTP Request
var dummyData1 = []string{"a", "b", "c", "d", "e", "f"}
var activeMap = new(int32)
var map1 = make(map[string]*int32)
var map2 map[string]*int32
var combinedMap = make(map[string]*int32)
func mapKeyUpdater () {
for _, key := range dummyData1 {
combinedMap[key] = new(int32)
map1[key] = new(int32)
}
atomic.AddInt32(activeMap, 1)
time.Sleep(3 * time.Second)
for {
if atomic.LoadInt32(activeMap) == 1 {
map2 = make(map[string]*int32)
for _, key := range dummyData1 {
map2[key] = new(int32)
}
atomic.AddInt32(activeMap, 1)
time.Sleep(500 * time.Millisecond) //Added after EDIT. See below
for key, count := range map1{
*combinedMap[key] += *count
}
} else {
map1 = make(map[string]*int32)
for _, key := range dummyData1 {
map1[key] = new(int32)
}
atomic.AddInt32(activeMap, -1)
time.Sleep(500 * time.Millisecond) //Added after EDIT. See below
for key, count := range map2 {
*combinedMap[key] += *count
}
}
time.Sleep(3 * time.Second)
}
}
func counter () {
for {
randomIndex := rand.Intn(5)
randomKey := dummyData1[randomIndex]
if atomic.LoadInt32(activeMap) == 1 {
val := atomic.AddInt32(map1[randomKey], 100)
fmt.Printf("Added 100 to %v in Map1. Updated value %v\n", randomKey, val)
} else {
val := atomic.AddInt32(map2[randomKey], 100)
fmt.Printf("Added 100 to %v in Map2. Updated value %v\n", randomKey, val)
}
}
}
func main () {
go mapKeyUpdater()
time.Sleep(500 * time.Millisecond)
go counter()
time.Sleep(15 * time.Second)
}
Now when I run this with the command go run -race raceBug.go
I get 4 Race's each time. However, it is clear from the output that there is no race, and the maps are working as intended
==================
Added 100 to e in Map2. Updated value 7990900
WARNING: DATA RACE
Write at 0x0000011cdbd0 by goroutine 7:
Added 100 to a in Map2. Updated value 7972000
main.mapKeyUpdater()
/raceBug.go:34 +0x14d
Previous read at 0x0000011cdbd0 by goroutine 9:
Added 100 to e in Map2. Updated value 7991000
[failed to restore the stack]
Goroutine 7 (running) created at:
main.main()
/raceBug.go:62 +0x29
Added 100 to e in Map2. Updated value 7991100
Goroutine 9 (running) created at:
main.main()
/raceBug.go:64 +0x44
==================
Added 100 to c in Map2. Updated value 7956400
Added 100 to b in Map2. Updated value 7993400
==================
WARNING: DATA RACE
Added 100 to e in Map1. Updated value 100
Read at 0x00c00001acec by goroutine 7:
main.mapKeyUpdater()
/raceBug.go:40 +0x2d4
Added 100 to c in Map1. Updated value 100
Previous write at 0x00c00001acec by goroutine 9:
sync/atomic.AddInt32()
/usr/local/Cellar/go/1.18/libexec/src/runtime/race_amd64.s:279 +0xb
sync/atomic.AddInt32()
<autogenerated>:1 +0x1a
Added 100 to d in Map1. Updated value 100
Goroutine 7 (running) created at:
main.main()
/raceBug.go:62 +0x29
Added 100 to b in Map1. Updated value 100
Goroutine 9 (running) created at:
main.main()
/raceBug.go:64 +0x44
==================
This article by an engineer at Google says - https://medium.com/@val_deleplace/does-the-race-detector-catch-all-data-races-1afed51d57fb
> If you strongly believe that you witnessed a false positive, then report a bug for the race detector. If you have good reasons to believe that the race condition was caused by the standard library or by the runtime (rather than your own code), then report a bug for the standard library or the runtime.
As I am still pretty new at Go, just want to get some confirmation of this.
EDIT: Just to make sure that the combinedMap
loop has enough time before it starts, I added a time.Sleep(500 * time.Millisecond)
. However the Race is still detected, but the output is now different.
New Output
==================
WARNING: DATA RACEAdded 100 to e in Map2. Updated value 9447300
Write at 0x0000011cdbd0 by goroutine 7:
Added 100 to c in Map2. Updated value 9465100
main.mapKeyUpdater()
/raceBug2.go:35 +0x14d
Previous read at 0x0000011cdbd0 by goroutine 9:
Added 100 to b in Map2. Updated value 9461300
[failed to restore the stack]
Goroutine 7 (running) created at:
main.main()
/raceBug2.go:64 +0x29
Added 100 to d in Map2. Updated value 9479400
Goroutine 9 (running) created at:
main.main()
/raceBug2.go:66 +0x44
Added 100 to c in Map2. Updated value 9465200
==================
答案1
得分: 1
数据竞争发生在两个 goroutine 同时访问同一个变量,并且至少有一个访问是写操作的情况下。
在你的代码中,全局变量 map1 和 map2 被两个 goroutine 访问。仅仅使用 atomic 包来读取和操作 map 的项是不够的,因为在 mapKeyUpdater 中重新创建 map 时,map 的项的值(int32 的指针)会发生变化。使用 Mutex 来锁定这两个 map 可以消除竞争。
package main
import (
"fmt"
"math/rand"
"sync"
"sync/atomic"
"time"
)
// 这些数据通常通过 HTTP 请求获取
var dummyData1 = []string{"a", "b", "c", "d", "e", "f"}
var activeMap = new(int32)
var combinedMap = make(map[string]*int32)
type myMap struct {
mutex sync.RWMutex
value map[string]*int32
}
var (
map1 = myMap{
value: make(map[string]*int32),
}
map2 = myMap{}
)
func mapKeyUpdater() {
for _, key := range dummyData1 {
combinedMap[key] = new(int32)
map1.mutex.Lock()
map1.value[key] = new(int32)
map1.mutex.Unlock()
}
atomic.AddInt32(activeMap, 1)
time.Sleep(3 * time.Second)
for {
if atomic.LoadInt32(activeMap) == 1 {
map2.mutex.Lock()
map2.value = make(map[string]*int32)
for _, key := range dummyData1 {
map2.value[key] = new(int32)
}
map2.mutex.Unlock()
atomic.AddInt32(activeMap, 1)
time.Sleep(500 * time.Millisecond) // 编辑后添加。参见下文
map1.mutex.Lock()
for key, count := range map1.value {
*combinedMap[key] += *count
}
map1.mutex.Unlock()
} else {
map1.mutex.Lock()
for _, key := range dummyData1 {
map1.value[key] = new(int32)
}
map1.mutex.Unlock()
atomic.AddInt32(activeMap, -1)
time.Sleep(500 * time.Millisecond) // 编辑后添加。参见下文
map2.mutex.Lock()
for key, count := range map2.value {
*combinedMap[key] += *count
}
map2.mutex.Unlock()
}
time.Sleep(3 * time.Second)
}
}
func counter() {
for {
randomIndex := rand.Intn(5)
randomKey := dummyData1[randomIndex]
if atomic.LoadInt32(activeMap) == 1 {
map1.mutex.Lock()
val := atomic.AddInt32(map1.value[randomKey], 100)
map1.mutex.Unlock()
fmt.Printf("在 Map1 中给 %v 添加了 100。更新后的值为 %v\n", randomKey, val)
} else {
map2.mutex.Lock()
val := atomic.AddInt32(map2.value[randomKey], 100)
map2.mutex.Unlock()
fmt.Printf("在 Map2 中给 %v 添加了 100。更新后的值为 %v\n", randomKey, val)
}
}
}
func main() {
go mapKeyUpdater()
time.Sleep(500 * time.Millisecond)
go counter()
time.Sleep(15 * time.Second)
}
应该避免使用共享变量(如全局变量)来向多个 goroutine 发送值。使用通道是推荐的方式。注意:使用通道传递指针也是不安全的。只需通过通道发送值即可。
英文:
A data race happens when two goroutines access the same variable concurrently, and at least one of the accesses is a write.
In your code, the global vars map1 and map2 are accessed by the two goroutines. Using atomic package to read and operate the map item is not enough because the map items value (pointer of int32) are changed when recreating the map in mapKeyUpdater. Use a Mutex to lock these two maps to eliminate race.
package main
import (
"fmt"
"math/rand"
"sync"
"sync/atomic"
"time"
)
//This data is normally fetched via HTTP Request
var dummyData1 = []string{"a", "b", "c", "d", "e", "f"}
var activeMap = new(int32)
var combinedMap = make(map[string]*int32)
type myMap struct {
mutex sync.RWMutex
value map[string]*int32
}
var (
map1 = myMap{
value: make(map[string]*int32),
}
map2 = myMap{}
)
func mapKeyUpdater() {
for _, key := range dummyData1 {
combinedMap[key] = new(int32)
map1.mutex.Lock()
map1.value[key] = new(int32)
map1.mutex.Unlock()
}
atomic.AddInt32(activeMap, 1)
time.Sleep(3 * time.Second)
for {
if atomic.LoadInt32(activeMap) == 1 {
map2.mutex.Lock()
map2.value = make(map[string]*int32)
for _, key := range dummyData1 {
map2.value[key] = new(int32)
}
map2.mutex.Unlock()
atomic.AddInt32(activeMap, 1)
time.Sleep(500 * time.Millisecond) //Added after EDIT. See below
map1.mutex.Lock()
for key, count := range map1.value {
*combinedMap[key] += *count
}
map1.mutex.Unlock()
} else {
map1.mutex.Lock()
for _, key := range dummyData1 {
map1.value[key] = new(int32)
}
map1.mutex.Unlock()
atomic.AddInt32(activeMap, -1)
time.Sleep(500 * time.Millisecond) //Added after EDIT. See below
map2.mutex.Lock()
for key, count := range map2.value {
*combinedMap[key] += *count
}
map2.mutex.Unlock()
}
time.Sleep(3 * time.Second)
}
}
func counter() {
for {
randomIndex := rand.Intn(5)
randomKey := dummyData1[randomIndex]
if atomic.LoadInt32(activeMap) == 1 {
map1.mutex.Lock()
val := atomic.AddInt32(map1.value[randomKey], 100)
map1.mutex.Unlock()
fmt.Printf("Added 100 to %v in Map1. Updated value %v\n", randomKey, val)
} else {
map2.mutex.Lock()
val := atomic.AddInt32(map2.value[randomKey], 100)
map2.mutex.Unlock()
fmt.Printf("Added 100 to %v in Map2. Updated value %v\n", randomKey, val)
}
}
}
func main() {
go mapKeyUpdater()
time.Sleep(500 * time.Millisecond)
go counter()
time.Sleep(15 * time.Second)
}
It should be avoided to using shared variables (like global var) to send value to multiple goroutines. Use channel is the recommended way. Note: using channel to pass pointer is not safe either. Just send value via channel.
Do not communicate by sharing memory; instead, share memory by communicating.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论