2016-08-19 7 views
3

Из the Dave Cheney Blog, следующий код, по-видимому, вызывает случай гонки, которые могут быть решены только путем изменения func (RPC) version() int к 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.

Я хотел бы понять следующее:

1) Почему это конкретная строка считается прочитанной на УИП структуру?

2) Почему смена RPC на *RPC решит проблему?

ответ

5

Если у вас есть метод с приемником значения, как это:

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, который будет принят к методу (используемому как значение приемника).

Таким образом, хотя один goroutine go rpc.compute() работает и изменение значения rpc структуры (rpc.result = 42), главный goroutine делает копию целого значения rpc структуры. Там! Это гонка.

При изменении типа приемника к указателю:

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(), оно использует только указатель в качестве приемника, поэтому n o копия производится из значения структуры rpc. И так как ничего из структуры не используется/читается в RPC.version(), нет гонки.

Примечание:

Обратите внимание, что если RPC.version() будет читать RPC.result поле, оно также будет гонка, так как один goroutine изменяет его в то время как основной goroutine бы читать:

func (rpc *RPC) version() int { 
    return rpc.result // RACE! 
} 

Примечание № 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 
} 
+0

Это проясняет все, спасибо – AJPennster