2017-02-03 5 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 - это указатель, а не значение. Если я изменю «МТХ» быть значение и программа запуска с 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 в этом случае вы больше не должны видеть гонки, как сейчас только указатель копируется и не базовое значение. Тем не менее, чтобы сделать намерения понятным, чище изменить тип приемника get() как указатель.

Вот хорошая вики вокруг той же самой - https://github.com/golang/go/wiki/CodeReviewComments#receiver-type

Краткий комментарий по методам: https://golang.org/ref/spec#Method_values

Смежные вопросы