2017-02-03 33 views
2

爲什麼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:獲得通行證鎖按值:main.Counter包含sync.Mutex`

這很有道理。

注:這個問題不是關於如何實現線程安全的計數器

link to playground code

回答

2

比賽是因爲對於get()方法的價值接收機。爲了調用get()方法,必須將結構的副本傳遞給方法表達式。沒有語法糖的方法調用看起來像:

value := Counter.get(*counter) 

複製結構需要讀取value領域,該方法之前,恰好可以把鎖,這就是爲什麼在比賽報道的方法調用的行而不是在方法中。

這就是爲什麼改變接收器到指針接收器將解決這個問題。此外,由於所有接收器都需要指針,所以mtx可以保留爲sync.Mutex值,因此不需要進行初始化。

1

作爲@JimB所指出的,在get()方法的情況下的副本被傳遞在這種情況下 字段值被首先讀取,然後被複制,而沒有任何鎖定並且由於 相同變量在inc()突變,所述競賽被檢測到。

爲了進一步說明這一點,你也可以改變字段value 類型的指針,即value *int在這種情況下,你應該不會再看到像現在 只有指針被複制的競爭,而不是潛在價值。也就是說,爲了使意圖 更清楚,將接收器類型更改爲指針是更清潔的。

這裏是圍繞相同的一個很好的維基 - https://github.com/golang/go/wiki/CodeReviewComments#receiver-type

述略上的方法: https://golang.org/ref/spec#Method_values

相關問題