英文:
Data race when reading field from struct value passed by value
问题
为什么Golang竞争检测器会对以下代码报错:
package main
import (
"fmt"
"sync"
)
type Counter struct {
value int
mtx *sync.Mutex
}
func NewCounter() *Counter {
return &Counter{0, &sync.Mutex{}}
}
func (c *Counter) inc() {
c.mtx.Lock()
c.value++
c.mtx.Unlock()
}
func (c Counter) get() int {
c.mtx.Lock()
res := c.value
c.mtx.Unlock()
return res
}
func main() {
var wg sync.WaitGroup
counter := NewCounter()
max := 100
wg.Add(max)
// consumer
go func() {
for i := 0; i < max; i++ {
value := counter.get()
fmt.Printf("counter value = %d\n", value)
wg.Done()
}
}()
// producer
go func() {
for i := 0; i < max; i++ {
counter.inc()
}
}()
wg.Wait()
}
当我使用-race
运行上述代码时,我得到以下警告:
==================
WARNING: DATA RACE
Read at 0x00c0420042b0 by goroutine 6:
main.main.func1()
main.go:39 +0x72
Previous write at 0x00c0420042b0 by goroutine 7:
main.(*Counter).inc()
main.go:19 +0x8b
main.main.func2()
main.go:47 +0x50
Goroutine 6 (running) created at:
main.main()
main.go:43 +0x167
Goroutine 7 (running) created at:
main.main()
main.go:49 +0x192
==================
如果我将func (c Counter) get() int
更改为func (c *Counter) get() int
,那么一切都正常工作。结果表明,get()
函数的接收器类型应该是指针。我对-copylocks
有所了解,但在这种情况下,mtx
是一个指针,而不是值。如果我将mtx
更改为值,并使用vet -copylocks
运行程序,我会得到以下警告:
main.go:23: get passes lock by value: main.Counter contains sync.Mutex
这是有道理的。
*注意:*这个问题不是关于如何实现线程安全计数器的问题。
英文:
Why does golang race detector complains about the following code:
package main
import (
"fmt"
"sync"
)
type Counter struct {
value int
mtx *sync.Mutex
}
func NewCounter() *Counter {
return &Counter {0, &sync.Mutex{}}
}
func (c *Counter) inc() {
c.mtx.Lock()
c.value++
c.mtx.Unlock()
}
func (c Counter) get() int {
c.mtx.Lock()
res := c.value
c.mtx.Unlock()
return res
}
func main() {
var wg sync.WaitGroup
counter := NewCounter()
max := 100
wg.Add(max)
// consumer
go func() {
for i := 0; i < max ; i++ {
value := counter.get()
fmt.Printf("counter value = %d\n", value)
wg.Done()
}
}()
// producer
go func() {
for i := 0; i < max ; i++ {
counter.inc()
}
}()
wg.Wait()
}
When I run the code above with -race
I'm getting the following warnings:
==================
WARNING: DATA RACE
Read at 0x00c0420042b0 by goroutine 6:
main.main.func1()
main.go:39 +0x72
Previous write at 0x00c0420042b0 by goroutine 7:
main.(*Counter).inc()
main.go:19 +0x8b
main.main.func2()
main.go:47 +0x50
Goroutine 6 (running) created at:
main.main()
main.go:43 +0x167
Goroutine 7 (running) created at:
main.main()
main.go:49 +0x192
==================
If I change func (c Counter) get() int
to func (c *Counter) get() int
then everything is working fine. It turns out that the receiver type for get()
function should be a pointer. And I'm confused why that is. I'm aware of "-copylocks" but in this case mtx
is a pointer, not value. If I change 'mtx' to be value and run program with vet -copylocks
I get this warning:
>main.go:23: get passes lock by value: main.Counter contains sync.Mutex`
That makes sense.
note: This question is not about how to implement thread safe counter
答案1
得分: 5
这个竞争是因为get()
方法的值接收器。为了调用get()
方法,必须将结构体的副本传递给方法表达式。没有语法糖的方法调用如下所示:
value := Counter.get(*counter)
复制结构体涉及读取value
字段,这发生在方法获取锁之前,这就是为什么竞争报告在方法调用的行上,而不是方法内部。
这就是为什么将接收器更改为指针接收器可以解决这个问题的原因。另外,由于所有接收器都需要是指针,所以mtx
可以保持为sync.Mutex
值,因此不需要初始化。
英文:
The race is because of the value receiver for the get()
method. In order to call the get()
method, a copy of the struct must be passed to the method expression. The method call without the syntactic sugar looks like:
value := Counter.get(*counter)
Copying the struct entails reading the value
field, which happens before the method can take the lock, which is why the race is reported on the line of the method call, rather than within the method.
This is why changing the receiver to a pointer receiver will fix the issue. Also, since all receivers need to be pointers, the mtx
can be left as a sync.Mutex
value so it doesn't need to be initialized.
答案2
得分: 1
正如@JimB指出的,在get()
方法的情况下,传递的是一个副本,因此首先读取字段值,然后进行复制,没有任何锁定,并且由于在inc()
中对相同的变量进行了修改,因此会检测到竞争条件。
为了进一步说明这一点,您还可以将字段value
的类型更改为指针,即value *int
,这样您就不会再看到竞争条件,因为现在只复制了指针而不是底层值。尽管如此,为了使意图更清晰,将get()
的接收器类型更改为指针会更加清晰。
这是一个关于相同问题的很好的维基页面 - https://github.com/golang/go/wiki/CodeReviewComments#receiver-type
关于方法的简要评论:
https://golang.org/ref/spec#Method_values
英文:
As @JimB points out, in the case of the get()
method a copy is passed in which case
the field value is read first and then copied, without any locking and since the
same variable is mutated in inc()
, the race is detected.
To further illustrate this point, you could also change the type of the field value
to a pointer i.e. value *int
in which case you should no longer see a race as now
only the pointer is copied and not the underlying value. That said, to make intentions
clearer, it is cleaner to change the get()
receiver type to be a pointer.
Here is a good wiki around the same - https://github.com/golang/go/wiki/CodeReviewComments#receiver-type
A brief commentary on methods:
https://golang.org/ref/spec#Method_values
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论