代码很简单,如下所示:

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)
}

案例:
  • 运行代码而不更改,您将得到“Count = 0”。没想到吗?
  • 仅取消注释第16行“fmt.Println(”开始++“)”;您将获得带有很多“开始++”和一些带有Count的值的输出,例如“Count = 11111”。预期的??
  • 仅取消注释第11行“var l sync.Mutex”,第15行“l.Lock()”和第18行“l.Unlock()”,并保留第16行的注释;您将得到类似“Count = 111111111”的输出。预期的。

  • 所以...我在共享变量中的用法有问题...?我的问题:
  • 为什么案例1的Count为0?
  • 如果预期情况1,为什么会发生情况2?

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

    最佳答案

    没有任何同步,您根本无法保证。
    可能有多种原因,为什么在第一种情况下您会看到“计数= 0”:

  • 您有一个多处理器或多核系统,一个单元(cpu或核)很高兴在for循环中搅动,而另一个 sleep 一秒钟并打印您随后看到的行。编译器生成机器码是完全合法的,该机器码将值加载到某个寄存器中并且仅在for循环中增加该寄存器。使用变量完成功能后,可以更新存储器位置。如果是无限循环,则永远不会。如您所见,程序员告诉编译器,通过忽略任何同步,该变量没有争执。
    在互斥体版本中,同步原语告诉编译器:
    可能还有其他线程使用该互斥锁,因此需要在解锁互斥锁之前将寄存器中的值写回内存位置。至少有人可以这样考虑。真正发生的情况是,解锁和随后的锁定操作在两个go例程之间的关系之前引入了一个发生,这可以保证,我们将看到在一个线程中的解锁操作之后,在另一个线程中的锁定操作之后,所有对变量的写入操作。 ,如go memory model locks中所述,无论如何实现。
  • 在主go例程中的 sleep 完成之前,Go运行时调度程序根本不会运行for循环。 (不太可能,但是,如果我没记错的话,我们不能保证不会发生这种情况。)可悲的是,没有太多关于调度程序如何进行的官方文档,但是它只能在一定的时间调度goroutine。要点,这并不是真正的先发制人。这样的后果是严重的。例如,您可以通过触发与内核一样多的go例程,使程序在go的某些版本中永久运行,无休止的for循环仅增加变量。主go例程(可能会结束程序)没有剩余的内核,并且调度程序无法在无尽for循环中抢先执行go例程,而该循环仅执行简单的操作(例如增加变量)。我不知道,如果现在更改。
  • 正如其他人指出的,
  • 是数据竞赛,将其搜索并阅读。

  • 您的版本之间的差异(仅第16行被注释/未注释)可能仅是由于运行时间所致,因为打印到终端的速度可能很慢。
    对于正确的程序,您需要在主程序中 sleep 之后,在fmt.Println之前锁定互斥锁,然后再对其进行解锁。但是对输出不能有确定的期望,因为结果会因机器/操作系统/而异。

    关于go - Goroutine中的变量未按预期更改,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44171945/

    10-12 04:00
    查看更多