英文:
Race condition even when using sync.Mutex in golang
问题
完整的代码在这里:https://play.golang.org/p/ggUoxtcv5m
go run -race main.go
表明代码中存在一个竞态条件,我无法解释。
尽管如此,程序输出的最终结果是正确的。
代码的核心部分如下:
type SafeCounter struct {
c int
sync.Mutex
}
func (c *SafeCounter) Add() {
c.Lock()
c.c++
c.Unlock()
}
var counter *SafeCounter = &SafeCounter{} // 全局变量
在 incrementor
函数中使用 *SafeCounter
:
func incrementor(s string) {
for i := 0; i < 20; i++ {
x := counter
x.Add()
counter = x
}
}
incrementor
方法在 main
函数中被调用两次:
func main() {
go incrementor()
go incrementor()
// 这里省略了一些与问题展示无关的其他代码,比如使用 waitGroup
}
所以,正如我所说的,go run -race main.go
总是会报告存在竞态条件。
此外,最终结果总是正确的(至少我已经运行了这个程序多次,它总是输出最终计数器为40,这是正确的)。
但是,程序在开始时打印出了不正确的值,所以你可能会看到类似以下的输出:
Incrementor1: 0 Counter: 2
Incrementor2: 0 Counter: 3
Incrementor2: 1 Counter: 4
// 其余部分是正确的
所以,缺少打印出 1
。
有人能解释一下为什么我的代码中存在竞态条件吗?
英文:
Complete code is here: https://play.golang.org/p/ggUoxtcv5m
go run -race main.go
says there is a race condition there which I fail to explain.
The program outputs correct final result, though.
The essence:
type SafeCounter struct {
c int
sync.Mutex
}
func (c *SafeCounter) Add() {
c.Lock()
c.c++
c.Unlock()
}
var counter *SafeCounter = &SafeCounter{} // global
use *SafeCounter
in incrementor:
func incrementor(s string) {
for i := 0; i < 20; i++ {
x := counter
x.Add()
counter = x
}
}
The incrementor
method is spawned twice in main
:
func main() {
go incrementor()
go incrementor()
// some other non-really-related stuff like
// using waitGroup is ommited here for problem showcase
}
So, as I said, go run -race main.go
will always say there is a race cond found.
Also, the final result is always correct (at least I've run this program for a number of times and it always say final counter is 40, which is correct).
BUT, the program prints incorrect values in the beginning so you can get something like:
Incrementor1: 0 Counter: 2
Incrementor2: 0 Counter: 3
Incrementor2: 1 Counter: 4
// ang the rest is ok
so, printing out 1
is missing there.
Can somebody explain why there is a race condition there is my code?
答案1
得分: 8
你有一些竞态条件,都是由竞态检测器指出的:
-
竞态 #1 是在
incrementor
中对counter
值进行读取和写入之间的竞争。 -
竞态 #2 是在
incrementor
中对counter
值进行并发写入之间的竞争。 -
竞态 #3 是在
fmt.Println
中对x.c
字段的读取和Add
方法中对x.c
的增加之间的竞争。
英文:
You have a number of race conditions, all pointed out specifically by the race detector:
x := counter // this reads the counter value without a lock
fmt.Println(&x.c)
x.Add()
counter = x // this writes the counter value without a lock
time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)
fmt.Println(s, i, "Counter:", x.c) // this reads the c field without a lock
-
race #1 is between the read and the write of the
counter
value inincrementor
-
race #2 is between the concurrent writes to the
counter
value inincrementor
-
race #3 is between the read of the
x.c
field infmt.Println
, and the increment tox.c
in theAdd
method.
答案2
得分: 1
读取和写入计数器指针的两行代码没有受到互斥锁的保护,并且会同时从多个goroutine中执行。
func incrementor(s string) {
for i := 0; i < 20; i++ {
x := counter // <-- 这里是指针读取
x.Add()
counter = x // <-- 这里是指针写入,可能存在竞争条件
}
}
英文:
The two lines that read and write the counter pointer are not protected by the mutex and are done concurrently from multiple goroutines.
func incrementor(s string) {
for i := 0; i < 20; i++ {
x := counter // <-- this pointer read
x.Add()
counter = x // <-- races with this pointer write
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论