在使用sync.Mutex进行同步时仍然存在竞态条件

huangapple go评论85阅读模式
英文:

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 = &amp;SafeCounter{} // global

use *SafeCounter in incrementor:

func incrementor(s string) {
	for i := 0; i &lt; 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(&amp;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, &quot;Counter:&quot;, x.c) // this reads the c field without a lock
  • race #1 is between the read and the write of the counter value in incrementor

  • race #2 is between the concurrent writes to the counter value in incrementor

  • race #3 is between the read of the x.c field in fmt.Println, and the increment to x.c in the Add 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 &lt; 20; i++ {
        x := counter  // &lt;-- this pointer read
        x.Add()
        counter = x   // &lt;-- races with this pointer write
    }
}

huangapple
  • 本文由 发表于 2017年5月4日 07:46:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/43771733.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定