16

我是Go语言的新手,所以如果我的问题非常基本,请原谅。我写了一个非常简单的代码:

func main(){
    var count int // Default 0

    cptr := &count

    go incr(cptr)

    time.Sleep(100)

    fmt.Println(*cptr)
}

// Increments the value of count through pointer var
func incr(cptr *int) {
    for i := 0; i < 1000; i++ {
            go func() {
                    fmt.Println(*cptr)
                    *cptr = *cptr + 1
            }()
      }
    }

count 的值应该随着循环运行的次数加一。考虑以下情况:

循环运行 100 次--> 计数值为 100(这是正确的,因为循环运行 100 次)。

循环运行 >510 次 --> 计数值是 508 或 510。即使它是 100000,也会发生这种情况。

我在 8 核处理器机器上运行它。

4

5 回答 5

16

首先:在 Go 1.5 之前,它在单个处理器上运行,仅使用多个线程来阻塞系统调用。除非您通过使用GOMAXPROCS告诉运行时使用更多处理器。

从 Go 1.5 开始,GOMAXPROCS 设置为 CPU 的数量。见6、7。_ _

此外,*cptr = *cptr + 1不能保证操作是原子的。如果仔细观察,它可以分为 3 个操作:通过取消引用指针获取旧值、递增值、将值保存到指针地址。

您获得 508/510 的事实是由于运行时中的一些魔法,而不是定义为保持这种状态。有关并发操作行为的更多信息可以在Go 内存模型中找到。
您可能会获得 <510 个启动的 goroutine 的正确值,因为低于这些值的任何数字都没有(还)被中断。

通常,您尝试做的事情既不推荐使用任何语言,也不推荐使用“Go”方式进行并发。使用通道进行同步的一个很好的例子是这段代码:Share Memory By Communicating(而不是通过共享内存进行通信)

这里有一个小例子来告诉你我的意思:使用一个缓冲区为 1 的通道来存储当前数字,当你需要它时从通道中获取它,随意更改它,然后将它放回去供其他人使用。

于 2014-01-08T11:31:15.040 回答
13

你的代码很活泼:你从不同的、不同步的 goroutine 写入相同的内存位置,没有任何锁定。结果基本上是不确定的。您必须要么 a) 确保所有 goroutine 以良好、有序的方式依次写入,或者 b) 通过例如 e 互斥锁或 c) 使用原子操作来保护每个写入。

如果你编写这样的代码:总是在比赛检测器下尝试$ go run -race main.go并修复所有比赛。

于 2014-01-08T10:49:08.180 回答
3

在这种情况下,使用通道的一个不错的替代方法可能是sync/atomic包,它包含用于原子递增/递减数字的特定函数。

于 2014-01-08T18:49:38.777 回答
2

您正在生成 500 或 1000 个例程,而例程之间没有同步。这创造了一个竞争条件,这使得结果不可预测。

想象一下,您在办公室工作,为您的老板核算费用余额。

你的老板正在与他的 1000 名下属开会,在这次会议上他说:“我会给你们所有人奖金,所以我们将不得不增加我们的开支记录”。他发出以下命令:i) 去 Nerve 询问他/她目前的费用余额是多少 ii) 打电话给我的秘书询问您将获得多少奖金 iii) 将您的奖金作为费用添加到额外费用余额中。自己算算。iv) 要求 Nerve 在我的授权下写下新的费用余额。

所有 1000 名热心的参与者都争先恐后地记录他们的奖金并创造了一个比赛条件。

假设有 50 个热切的地鼠同时(几乎)击中 Nerve,他们会问,i)“当前的费用余额是多少?--Nerve 对所有这 50 个地鼠说 1000 美元,正如他们在同一个问题上所问的那样在同一时间(几乎)余额为 1000 美元。ii)然后地鼠打电话给秘书,应该给我多少奖金?秘书回答:“公平一点,只要 1 美元”iii)嘘声说算一算,他们都计算出 1000 美元+ 1 美元 = 1001 美元应该是公司的新成本余额 iv) 他们都会要求 Nerve 将 1001 美元返还到余额中。

您看到 Eager gophers 方法中的问题了吗?完成了 50 美元的计算单元,每次在现有余额中增加 1 美元,但成本并没有增加 50 美元;只增加了 1 美元。

希望能澄清问题。现在对于解决方案,其他贡献者提供了非常好的解决方案,我相信这已经足够了。

于 2021-02-15T17:51:35.967 回答
0

所有这些方法对我来说都失败了,noobie here. 但我找到了更好的方法http://play.golang.org/p/OcMsuUpv2g

我正在使用同步包来解决该问题并等待所有 goroutines 完成,没有睡眠或通道。

别忘了看看那个很棒的帖子http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/

于 2015-08-21T15:19:54.887 回答