goroutines如何在多核处理器上运行

时间:2014-01-08 10:37:20

标签: parallel-processing go goroutine

我是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次 - &gt; count的值是100(这是正确的,因为循环运行100次)。

循环运行> 510次 - &gt; count的值是508或510.即使它是100000也会发生。

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

5 个答案:

答案 0 :(得分:11)

你的代码是活泼的:你从不同的,不同步的goroutines写入相同的内存位置而没有任何锁定。结果基本上是未定义的。你必须要么a)确保所有goroutine以一种漂亮,有序的方式相互写入,或者b)保护每一次写入,例如: e互斥或c)使用原子操作。

如果您编写此类代码:请始终在$ go run -race main.go等竞赛检测器下进行尝试并修复所有比赛。

答案 1 :(得分:10)

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

从Go 1.5开始,GOMAXPROCS被设置为CPUS的数量。请参阅67

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

您获得508/510这一事实是由于运行时的一些魔力并未定义为保持这种状态。有关并发操作行为的更多信息可以在Go memory model中找到 您可能正在为&lt; 510启动goroutine获取正确的值,因为这些以下的任何数字都没有(但)被中断。

一般来说,您尝试做的事情既不是任何语言都推荐的,也不是&#34; Go&#34;做并发的方法。使用通道进行同步的一个很好的例子就是这个代码遍历:Share Memory By Communicating(而不是通过共享内存进行通信)

Here is a little example to show you what I mean:使用缓冲区为1的通道存储当前数字,在需要时从通道中获取,随意更改,然后将其放回供其他人使用。

答案 2 :(得分:2)

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

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

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

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

说有 50 个热切的地鼠同时(几乎)击中了 Nerve,他们问道, i) “目前的费用余额是多少? -- Nerve 对所有这 50 个地鼠说 1000 美元,因为他们同时(几乎)问了同一个问题,当时余额为 1000 美元。 ii) 然后地鼠们打电话给秘书,应该给我多少奖金? 秘书回答说:“公平地说,只要 1 美元” iii) Boos 说算一算,他们都计算出 $1000+ $1 = $1001 应该是公司的新成本余额 iv) 他们都会要求 Nerve 退还 1001 美元的余额。

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

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

答案 3 :(得分:1)

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

答案 4 :(得分:0)

所有这些方法都不对我,noobie here。但我找到了一种更好的方式http://play.golang.org/p/OcMsuUpv2g

我正在使用同步包来解决这个问题并等待所有goroutine完成,没有Sleep或Channel。

不要忘记看一下那篇很棒的帖子http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/