为什么种族探测器没有检测到这种种族状况?

Gij*_*ong 1 concurrency multithreading go race-condition goroutine

我目前正在学习Go编程语言,现在正在尝试原子包。

在此示例中,我产生了许多Goroutine,它们都需要增加包级变量。有几种避免竞争情况的方法,但是现在我想使用atomic软件包解决此问题。

在Windows PC(go run main.go)上运行以下代码时,结果不是我期望的结果(我希望最终结果是1000)。最终数字在900到1000之间。在Go Playground中运行代码时,该值为1000。

这是我正在使用的代码:https : //play.golang.org/p/8gW-AsKGzwq

var counter int64
var wg sync.WaitGroup

func main() {
    num := 1000
    wg.Add(num )
    for i := 0; i < num ; i++ {
        go func() {
            v := atomic.LoadInt64(&counter)
            v++
            atomic.StoreInt64(&counter, v)

            // atomic.AddInt64(&counter, 1)

            // fmt.Println(v)
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println("final", counter)
}
Run Code Online (Sandbox Code Playgroud)
go run main.go
final 931

go run main.go
final 960

go run main.go
final 918
Run Code Online (Sandbox Code Playgroud)

我本来希望比赛检测器会给出错误,但是不会:

go run -race main.go
final 1000
Run Code Online (Sandbox Code Playgroud)

并输出正确的值(1000)。

我正在使用Go版本go1.12.7 windows/amd64(目前是最新版本)

我的问题:

  • 为什么竞速检测器没有给出错误,但是在没有竞速检测器的情况下运行代码时,我看到了不同的值吗?
  • 我的理论为何“装入/存储”组合不起作用的原因是,这两个原子调用不是一个整体的原子。在这种情况下,我应该使用该atomic.AddInt64方法,对吗?

任何帮助将不胜感激 :)

icz*_*cza 7

您的代码中没有任何内容,所以这就是为什么种族检测器什么也没检测到。您的counter变量始终是通过atomic已启动的goroutine中的程序包访问的,而不是直接访问的。

之所以有时得到1000而有时得到更少的原因是由于运行goroutines的活动线程的数量:GOMAXPROCS。在Go Playground上,它是1,因此任何时候您都可以使用一个活动的goroutine(因此,基本上,您的应用程序是按顺序执行的,没有任何并行性)。当前的goroutine调度程序不会将goroutine随意停放。

在本地机器上你可能有一个多核CPU,并GOMAXPROCS默认为可用的逻辑CPU数,所以GOMAXPROCS大于1,所以你必须运行多个够程并行(完全并行的,不只是并发)。

看到这个片段:

v := atomic.LoadInt64(&counter)
v++
atomic.StoreInt64(&counter, v)
Run Code Online (Sandbox Code Playgroud)

加载counter值并将其分配给它,然后v递增v,然后存储回递增的值v。如果两个并行goroutine同时执行此操作会怎样?假设两个都加载了值100。两者都会增加其本地副本:101。两者都写回101,即使应该在102

是的,原子地递增计数器的正确方法是这样使用atomic.AddInt64()

for i := 0; i < num; i++ {
    go func() {
        atomic.AddInt64(&counter, 1)
        wg.Done()
    }()
}
Run Code Online (Sandbox Code Playgroud)

这样,无论是什么,您将始终获得1000 GOMAXPROCS

  • @mangusta从理论上讲,您也可以使用1个线程获得少于1000个的线程,但是当前的goroutine调度程序不会将goroutines随意放置。因此,您期望的“上下文切换”不存在。 (3认同)
  • @mangusta:竞争检测器仅检测数据竞争,而这绝对不是数据竞争。您可以将其视为某种“逻辑”竞赛,但该程序执行的却完全符合预期。实际上,如果goroutine都在加载和存储之间的同一点处屈服,那么即使在这里“ 1”也是有效的输出。 (3认同)