goroutines在多核处理器上的行为如何?

huangapple go评论87阅读模式
英文:

How does goroutines behave on a multi-core processor

问题

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

func main(){
    var count int // 默认为0

    cptr := &count

    go incr(cptr)

    time.Sleep(100)

    fmt.Println(*cptr)
}

// 通过指针变量增加count的值
func incr(cptr *int) {
    for i := 0; i < 1000; i++ {
        go func() {
            fmt.Println(*cptr)
            *cptr = *cptr + 1
        }()
    }
}

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

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

循环运行>510次 --> count的值为508或510。即使循环运行100000次也是如此。

我在一台8核处理器的机器上运行这个代码。

英文:

I am a newbie in Go language, so please excuse me if my question is very basic. I have written a very simple code:

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

    cptr := &amp;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 &lt; 1000; i++ {
            go func() {
                    fmt.Println(*cptr)
                    *cptr = *cptr + 1
            }()
      }
    }

The value of count should increment by one the number of times the loop runs. Consider the cases:

Loop runs for 100 times--> value of count is 100 (Which is correct as the loop runs 100 times).

Loop runs for >510 times --> Value of count is either 508 OR 510. This happens even if it is 100000.

I am running this on an 8 core processor machine.

答案1

得分: 18

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

从Go 1.5开始,GOMAXPROCS设置为CPU的数量。参见67

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

你得到508/510的结果是由于运行时中的某些魔法,而不是定义为保持这种方式。有关并发操作行为的更多信息,请参阅Go内存模型

你可能会得到小于510的正确值,因为任何小于这些值的数字都没有(尚未)被中断。

一般来说,你尝试做的事情既不推荐在任何语言中使用,也不是"Go"并发的方式。一个非常好的使用通道进行同步的示例是这个代码示例:通过通信共享内存(而不是通过共享内存进行通信)。

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

英文:

First of all: prior to Go 1.5 it runs on a single processor, only using multiple threads for blocking system calls. Unless you tell the runtime to use more processors by using GOMAXPROCS.

As of Go 1.5 GOMAXPROCS is set to the number of CPUS. See 6, 7 .

Also, the operation *cptr = *cptr + 1 is not guaranteed to be atomic. If you look carefully, it can be split up into 3 operations: fetch old value by dereferencing pointer, increment value, save value into pointer address.

The fact that you're getting 508/510 is due to some magic in the runtime and not defined to stay that way. More information on the behaviour of operations with concurrency can be found in the Go memory model.
You're probably getting the correct values for <510 started goroutines because any number below these are not (yet) getting interrupted.

Generally, what you're trying to do is neither recommendable in any language, nor the "Go" way to do concurrency. A very good example of using channels to synchronize is this code walk: Share Memory By Communicating (rather than communicating by sharing memory)

Here is a little example to show you what I mean: use a channel with a buffer of 1 to store the current number, fetch it from the channel when you need it, change it at will, then put it back for others to use.

答案2

得分: 13

你的代码存在竞争条件:你在不同的、未同步的goroutine中向同一内存位置写入数据,没有进行任何锁定。结果基本上是不确定的。你必须要么a) 确保所有的goroutine按照一种良好有序的方式进行写入,要么b) 通过使用互斥锁(例如mutex)来保护每次写入,要么c) 使用原子操作。

如果你编写这样的代码:一定要在race检测器下进行测试,例如$ go run -race main.go,并修复所有的竞争条件。

英文:

You code is racy: You write to the same memory location from different, unsynchronized goroutines without any locking. The result is basically undefined. You must either a) make sure that all the goroutine writes after each other in a nice, ordered way, or b) protect each write by e.g. e mutex or c) use atomic operations.

If you write such code: Always try it under the race detector like $ go run -race main.go and fix all races.

答案3

得分: 3

在这种情况下,一个不错的替代方案可能是使用 sync/atomic 包,该包中包含了专门用于原子性地增加/减少数字的函数。

英文:

A nice alternative to using channels in this case might be the sync/atomic package, which contains specifically functions for atomically incrementing/decrementing numbers.

答案4

得分: 2

您正在创建500或1000个没有同步的例程。这会产生竞争条件,使结果变得不可预测。

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

您的老板在与他的1000名下属开会时说:“我会给你们所有人发奖金,所以我们需要增加我们的费用记录。”他发布了以下命令:
i)去问Nerve当前的费用余额是多少
ii)打电话给我的秘书询问你将获得多少奖金
iii)将您的奖金作为额外费用添加到费用余额中。自己做数学计算。
iv)要求Nerve根据我的授权写下新的费用余额。

所有1000个热切的参与者都争先恐后地记录他们的奖金,从而创建了竞争条件。

假设有50只热切的gophers几乎同时访问了Nerve,他们问道:
i)“当前的费用余额是多少?

  • Nerve对这50只gophers都说余额是1000美元,因为他们几乎同时(几乎)在余额为1000美元时提出了同样的问题。
    ii)然后,这些gophers打电话给秘书,问应该支付多少奖金?
    秘书回答:“只要1美元就可以了”
    iii)老板说做数学计算,他们都计算出1000美元+1美元=1001美元应该是公司的新费用余额
    iv)他们都会要求Nerve将1001美元放回余额中。

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

希望这样解释清楚了问题。至于解决方案,其他贡献者提供了非常好的解决方案,我相信这已经足够了。

英文:

You are spawning 500 or 1000 routines without synchronization among routines. This is creating a race condition, which makes the result un-predictable.

Imagine You are working in an office to account for expense balance for your boss.

Your boss was in a meeting with 1000 of his subordinates and in this meeting he said, "I would pay you all a bonus, so we will have to increment the record of our expense". He issued following command:
i) Go to Nerve and ask him/ her what is the current expense balance
ii) Call my secretary to ask how much bonus you would receive
iii) Add your bonus as an expense to the additional expense balance. Do the math yourself.
iv) Ask Nerve to write the new balance of expense on my authority.

All of the 1000 eager participants rushed to record their bonus and created a race condition.

Say 50 of the eager gophers hit Nerve at the same time (almost), they ask,
i) "What is the current expense balance?
-- Nerve says $1000 to all of those 50 gophers, as they asked at the same question at the same time(almost) when the balance was $1000.
ii) The gophers then called secretary, how much bonus should be paid to me?
Secretary answers, "just $1 to be fair"
iii) Boos said do the math, they all calculates $1000+ $1 = $1001 should be the new cost balance for the company
iv) They all will ask Nerve to put back $1001 to the balance.

You see the problem in the method of Eager gophers?
There are $50 units of computation done, every time the added $1 to the existing balance, but the cost didn't increase by $50; only increased by $1.

Hope that clarifies the problem. Now for the solutions, other contributors gave very good solutions that would be sufficient I believe.

答案5

得分: 0

所有这些方法对我来说都失败了,我是个新手。但是我找到了一种更好的方法http://play.golang.org/p/OcMsuUpv2g

我正在使用sync包来解决这个问题,并等待所有的goroutine完成,而不需要使用Sleep或Channel。

还有,不要忘记查看这篇很棒的文章http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/

英文:

All of that approaches failed to me, noobie here. But i have found a better way http://play.golang.org/p/OcMsuUpv2g

I'm using sync package to solve that problem and wait for all goroutines to finish, without Sleep or Channel.

And don't forget to take a look at that awesome post http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/

huangapple
  • 本文由 发表于 2014年1月8日 18:37:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/20993139.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定