Goroutine 中的变量没有按预期更改

The variable in Goroutine not changed as expected

代码简单如下:

package main

import (
        "fmt"
        //      "sync"
        "time"
)

var count = uint64(0)

//var l sync.Mutex

func add() {
        for {
                //              l.Lock()
                //              fmt.Println("Start ++")
                count++
                //              l.Unlock()
        }
}

func main() {
        go add()
        time.Sleep(1 * time.Second)
        fmt.Println("Count =", count)
}

案例:

  1. 运行代码不变,你会得到"Count = 0"。没想到??
  2. 只取消第 16 行的注释 "fmt.Println("开始 ++")";你会得到很多 "Start ++" 的输出和一些像 "Count = 11111" 这样的计数值。预计??
  3. 仅取消第 11 行 "var l sync.Mutex"、第 15 行 "l.Lock()" 和第 18 行 "l.Unlock()" 的注释,并保留第 16 行的注释;你会得到像 "Count = 111111111" 这样的输出。预期。

所以...我在共享变量中的使用有问题...?我的问题:

  1. 为什么案例 1 的计数为 0?
  2. 如果情况 1 是预期的,为什么情况 2 发生了?

环境: 1.go版本go1.8linux/amd64 2. 3.10.0-123.el7.x86_64 3. CentOS Linux 7.0.1406 版(核心版)

如果没有任何同步,您就没有任何保证。

可能有多种原因导致您在第一个案例中看到 'Count = 0':

  1. 你有一个多处理器或多核系统,一个单元(cpu 或核心)在 for 循环中愉快地运转,而另一个休眠一秒钟并打印你之后看到的线。编译器生成机器代码是完全合法的,它将值加载到某个寄存器中,并且只会在 for 循环中增加该寄存器。当函数完成变量时,可以更新内存位置。在无限循环的情况下,那永远不会。正如你,程序员告诉编译器,通过省略任何同步,没有关于该变量的争用。

    在你的互斥锁版本中,同步原语告诉编译器, 可能有一些其他线程占用了互斥量,因此它需要在解锁互斥量之前将值从寄存器写回到内存位置。至少可以这样想。解锁和稍后的锁定操作在两个 go 例程之间引入了发生在关系之前的实际情况,这提供了保证,我们将在另一个线程中的锁定操作之后看到一个线程中解锁之前对变量的所有写入,如 go memory model locks 中所述,无论这是如何实现的。

  2. Go 运行time 调度程序根本不会 运行 for 循环,直到主 go 例程中的睡眠完成。 (不太可能,但是,如果我没记错的话,不能保证这不会发生。)可悲的是,关于调度程序如何在 go 中工作的官方文档并不多,但它只能在特定时间安排一个 goroutine点,它不是真正的先发制人。这样做的后果很严重。例如,你可以让你的程序在某些版本的 go 中永远 运行 ,通过启动尽可能多的 go 例程,因为你有核心,做无限循环只增加一个变量。主要的 go 例程没有核心(这可能会结束程序),并且调度程序无法在无休止的 for 循环中抢占 go 例程,只做一些简单的事情,比如递增一个变量。不知道现在有没有变。

  3. 正如其他人所指出的,那是一场数据竞赛,google 它并阅读它。

你的版本之间的差异只有第 16 行是 commented/uncommented 可能只是因为 运行 时间,因为打印到终端可能会很慢。

对于正确的程序,您需要在主程序睡眠之后和 fmt.Println 之前额外锁定互斥锁,然后再将其解锁。但是不能对输出有确定性的期望,因为结果会随 machine/os/...

而变化

您在 count 上有数据竞争。结果未定义。

package main

import (
    "fmt"
    //      "sync"
    "time"
)

var count = uint64(0)

//var l sync.Mutex

func add() {
    for {
        //              l.Lock()
        //              fmt.Println("Start ++")
        count++
        //              l.Unlock()
    }
}

func main() {
    go add()
    time.Sleep(1 * time.Second)
    fmt.Println("Count =", count)
}

输出:

$ go run -race racer.go
==================
WARNING: DATA RACE
Read at 0x0000005995b8 by main goroutine:
  runtime.convT2E64()
      /home/peter/go/src/runtime/iface.go:255 +0x0
  main.main()
      /home/peter/gopath/src/so/racer.go:25 +0xb9

Previous write at 0x0000005995b8 by goroutine 6:
  main.add()
      /home/peter/gopath/src/so/racer.go:17 +0x5c

Goroutine 6 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:23 +0x46
==================
Count = 42104672
Found 1 data race(s)
$ 

参考文献:

Benign data races: what could possibly go wrong?