首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Goroutine中的变量未按预期更改

Goroutine中的变量未按预期更改
EN

Stack Overflow用户
提问于 2017-05-25 03:47:37
回答 2查看 1.2K关注 0票数 1

这些守则简单如下:

代码语言:javascript
复制
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. 运行代码而不更改,u将得到"Count = 0“。没料到?
  2. 只有取消注释第16行"fmt.Println("Start ++")";u将获得输出,其中包含大量的"Start ++“,还有一些具有计数的值,如"Count = 11111”。预期??
  3. 只有取消注释第11行"var l sync.Mutex“、第15行"l.Lock()”和第18行"l.Unlock()“,并保留第16行注释;u将得到类似于"Count = 111111111”的输出。期望的。

所以..。我在共享变量中的用法出了什么问题.?我的问题是:

  1. 为什么案件1有0和计数?
  2. 如果案件1是预期的,为什么会发生案例2?

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

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-05-25 05:54:28

没有任何同步,你就没有任何保证。

可能有多种原因,在第一种情况下可以看到“Count=0”:

  1. 您有一个多处理器或多核系统,一个单元(cpu或核心)在for循环中愉快地旋转,而另一个单元则睡上一秒,然后打印您正在看到的行。编译器生成机器代码是完全合法的,它将值加载到某个寄存器中,并且只会在for循环中增加该寄存器。当函数使用变量完成时,可以更新内存位置。如果有一个无限的for循环,那是绝对不会的。正如你,程序员告诉编译器,没有争议的那个变量,通过省略任何同步。 在互斥锁版本中,同步原语告诉编译器,可能有其他线程获取互斥锁,因此在解锁互斥锁之前,它需要将值从寄存器写回内存位置。至少有一个人可以这样想。真正发生的情况是,解锁和稍后的锁操作在两个go例程之间的关系之前引入一个事件,这提供了保证,即我们将看到在另一个线程中的锁操作之后,在一个线程解锁之前对变量的所有写操作,无论这是如何实现的。
  2. 在主Go例程中的睡眠完成之前,go运行时调度程序根本不会运行for循环。(不太可能,但如果我没记错的话,就不能保证不会发生这种情况。)遗憾的是,关于调度程序在go中的工作方式,没有太多的官方文档可供使用,但是它只能在特定的时间点调度一个大猩猩,这并不是真正的抢占。这样做的后果是严重的。例如,您可以让程序在go的某些版本中永远运行,方法是启动尽可能多的go例程,就像您有内核一样,为循环做无穷无尽的操作,只增加一个变量。主go例程(它可以结束程序)没有剩下的核心,调度程序不能在一个没完没了的for循环中抢占一个go例程,只做一些简单的事情,比如递增一个变量。我不知道,现在是不是变了。
  3. 正如其他人指出的那样,这是一个数据竞赛,搜索它并阅读它。

您的版本之间的差异--只有第16行是注释/未注释--可能只是因为运行时,因为到终端的打印可能非常慢。

对于一个正确的程序,您需要在您的主程序中睡眠之后和在fmt.Println之前另外锁定互斥锁,然后再解锁它。但是对于输出不可能有一个确定的期望,因为结果会随机器/os/.

票数 1
EN

Stack Overflow用户

发布于 2017-05-25 05:54:36

您可以在count上进行数据竞争。结果不明确。

代码语言:javascript
复制
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)
}

输出:

代码语言:javascript
复制
$ 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)
$ 

参考文献:

良性数据竞赛:什么可能会出错?

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44171945

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档