繁体   English   中英

从按值传递的结构值读取字段时发生数据争用

[英]Data race when reading field from struct value passed by value

为什么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'更改为value并使用vet -copylocks运行程序, vet -copylocks收到以下警告:

main.go:23:按值获取通行证锁定:main.Counter包含sync.Mutex`

那讲得通。

注意:此问题不是关于如何实现线程安全计数器的

链接到游乐场代码

出现这种情况的原因是get()方法的值接收者。 为了调用get()方法,必须将结构的副本传递给方法表达式。 没有语法糖的方法调用如下所示:

value := Counter.get(*counter)

复制该结构需要读取value字段,该字段发生在方法获得锁定之前,这就是为什么在方法调用的行而不是在方法内部报告竞争的原因。

这就是为什么将接收器更改为指针接收器将解决此问题的原因。 另外,由于所有接收者都需要是指针,因此mtx可以保留为sync.Mutex值,因此不需要初始化。

正如@JimB所指出的,在使用get()方法的情况下,将传递一个副本,在这种情况下,将首先读取字段值,然后将其复制,而无需任何锁定,并且由于在inc()对相同的变量进行了更改,因此竞争是检测。

为了进一步说明这一点,您还可以将字段value的类型更改为指针,即value *int在这种情况下,您将不再看到竞争,因为现在仅复制了指针,而不复制了基础值。 就是说,为了使意图更清楚,将get()接收器类型更改为指针会更清洁。

这是一个很好的维基-https: //github.com/golang/go/wiki/CodeReviewComments#receiver-type

关于方法的简短评论: https : //golang.org/ref/spec#Method_values

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM