为什么不读取/写入其内容的结构的方法仍会导致竞争情况?

Anf*_*nee 5 concurrency struct go race-condition goroutine

戴夫·切尼博客,下面的代码显然会导致比赛的情况下仅通过改变来解决func (RPC) version() intfunc (*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)
}
Run Code Online (Sandbox Code Playgroud)

在查看代码几次之后,我很难相信代码有一个种族案例.但是,当使用--race运行时,它声称有一个写入rpc.result=42和之前的读取version := rpc.version().我理解写作,因为goroutine改变了它的值rpc.result,但读取了什么呢?该version()方法在何处进行读取?它不会触及任何rpc的值,只返回1.

我想了解以下内容:

1)为什么特定行被认为是对rpc结构的读取?

2)为什么要改变RPC*RPC解决比赛案例?

icz*_*cza 7

当你有一个像这样的值接收器的方法:

func (RPC) version() int {
    return 1 // never going to need to change this
}
Run Code Online (Sandbox Code Playgroud)

你称之为这种方法:

version := rpc.version() // grab some other information while we're waiting
Run Code Online (Sandbox Code Playgroud)

必须从值中创建一个副本,该值rpc将传递给方法(用作接收器值).

因此,当一个goroutine go rpc.compute()正在运行并且正在修改rpcstruct value(rpc.result = 42)时,主goroutine正在复制整个rpcstruct值.那里!这是一场比赛.

将接收器类型修改为指针时:

func (*RPC) version() int {
    return 1 // never going to need to change this
}
Run Code Online (Sandbox Code Playgroud)

你称之为这种方法:

version := rpc.version() // grab some other information while we're waiting
Run Code Online (Sandbox Code Playgroud)

这是一个简写

version := (&rpc).version()
Run Code Online (Sandbox Code Playgroud)

这会将rpc值的地址传递给RPC.version()它,它只使用指针作为接收器,因此不会复制rpcstruct值.因为结构中没有任何东西被使用/读入RPC.version(),所以没有种族.

注意:

请注意,如果RPC.version()将读取该RPC.result字段,它也将是一个竞赛,因为一个goroutine修改它,而主goroutine将读取它:

func (rpc *RPC) version() int {
    return rpc.result // RACE!
}
Run Code Online (Sandbox Code Playgroud)

笔记2:

另请注意,如果RPC.version()读取的其他字段RPC未被修改RPC.compute(),则不会是竞赛,例如:

type RPC struct {
    result int
    done   chan struct{}
    dummy  int
}

func (rpc *RPC) version() int {
    return rpc.dummy // Not a race
}
Run Code Online (Sandbox Code Playgroud)