问题描述
来自戴夫·切尼博客,以下代码显然会导致种族问题,仅通过将 func(RPC)version()int
更改为 func(* RPC)version()int
即可解决种族问题:
From the Dave Cheney Blog, the following code apparently causes a race case that can be resolved merely by changing func (RPC) version() int
to func (*RPC) version() int
:
package main
import (
"fmt"
"time"
)
type RPC struct {
result int
done chan struct{}
}
func (rpc *RPC) compute() {
time.Sleep(time.Second) // strenuous computation intensifies
rpc.result = 42
close(rpc.done)
}
func (RPC) version() int {
return 1 // never going to need to change this
}
func main() {
rpc := &RPC{done: make(chan struct{})}
go rpc.compute() // kick off computation in the background
version := rpc.version() // grab some other information while we're waiting
<-rpc.done // wait for computation to finish
result := rpc.result
fmt.Printf("RPC computation complete, result: %d, version: %d\n", result, version)
}
在查看了几次代码之后,我很难相信代码具有竞赛用例.但是,使用--race运行时,它声称在 rpc.result = 42
处有一个写入,而在 version:= rpc.version()
处有一个先前的读取.我了解写操作,因为goroutine更改了 rpc.result
的值,但是读操作如何?读取在 version()
方法中的何处进行?它不涉及任何rpc值,仅返回1.
After looking over the code a few times, I was having a hard time believing that the code had a race case. However, when running with --race, it claims that there was a write at rpc.result=42
and a previous read at version := rpc.version()
. I understand the write, since the goroutine changes the value of rpc.result
, but what about the read? Where in the version()
method does the read occur? It does not touch any of the values of rpc, just returning 1.
我想了解以下内容:
1)为什么将特定行视为rpc结构上的读?
1) Why is that particular line considered a read on the rpc struct?
2)为什么将 RPC
更改为 * RPC
可以解决种族问题?
2) Why would changing RPC
to *RPC
resolve the race case?
推荐答案
当您的方法具有这样的值接收者时:
When you have a method with value receiver like this:
func (RPC) version() int {
return 1 // never going to need to change this
}
您调用此方法:
version := rpc.version() // grab some other information while we're waiting
必须从值 rpc
复制一个副本,该副本将被传递给方法(用作接收者的值).
A copy has to be made from the value rpc
, which will be passed to the method (used as the receiver value).
因此,当一个goroutine go rpc.compute()
正在运行并且正在修改 rpc
结构值( rpc.result = 42
)时,主要的goroutine正在复制整个 rpc
结构值.那里!这是一场比赛.
So while one goroutine go rpc.compute()
is running and is modifying the rpc
struct value (rpc.result = 42
), the main goroutine is making a copy of the whole rpc
struct value. There! It's a race.
当您将接收器类型修改为指针时:
When you modify the receiver type to pointer:
func (*RPC) version() int {
return 1 // never going to need to change this
}
您调用此方法:
version := rpc.version() // grab some other information while we're waiting
这是简写
version := (&rpc).version()
这会将 rpc
值的地址传递给 RPC.version()
,它仅使用指针作为接收器,因此不会复制 rpc
结构值.而且由于没有使用/读取 RPC.version()
中的结构,因此没有任何竞争.
This passes the address of the rpc
value to RPC.version()
, it uses only the pointer as the receiver, so no copy is made of the rpc
struct value. And since nothing from the struct is used / read in RPC.version()
, there is no race.
注意:
请注意,如果 RPC.version()
会读取 RPC.result
字段,那也将是一场竞赛,因为一个goroutine会对其进行修改,而主goroutine会对其进行修改阅读:
Note that if RPC.version()
would read the RPC.result
field, it would also be a race, as one goroutine modifies it while the main goroutine would read it:
func (rpc *RPC) version() int {
return rpc.result // RACE!
}
注释2:
还要注意,如果 RPC.version()
将读取 RPC
的另一个字段,而该字段在 RPC.compute()
中未修改,那不会是一场比赛,例如:
Also note that if RPC.version()
would read another field of RPC
which is not modified in RPC.compute()
, that would not be a race, e.g.:
type RPC struct {
result int
done chan struct{}
dummy int
}
func (rpc *RPC) version() int {
return rpc.dummy // Not a race
}
这篇关于为什么不读/写其内容的结构方法仍然导致竞争情况?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!