Go协程如何完美地交错执行是如何实现的?

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

How is it possible that go routines interleave perfectly?

问题

我很惊讶地发现Go协程似乎完美地交错执行...看到这个之后,我开始相信有一些关于内部机制的信息我还没有学到。例如:

$ go run x.go > output
$ grep ping output | wc -l
404778
$ grep pong output | wc -l
404777
$ cat x.go 
package main
import (
    "fmt"
    "time"
)

type Ball struct{ hits int }

func main() {
    table := make(chan *Ball)
    go player("ping", table)
    go player("pong", table)

    table <- new(Ball) // game on; toss the ball
    time.Sleep(1 * time.Second)
    <-table // game over; grab the ball
}

func player(name string, table chan *Ball) {
    for {
        ball := <-table
        ball.hits++
        fmt.Println(name, ball.hits)
        //time.Sleep(1 * time.Millisecond)
        table <- ball
    }
}

无论你在player函数中设置超时时间(或者完全删除它),你总是会得到 #ping == #ping +/- 1。

英文:

I'm surprised that go routines seem to interleave perfectly... After seeing this, I am starting to believe there's some missing information about the internals that I haven't learned about yet. Example:

$ go run x.go &gt; output
$ grep ping output | wc -l
404778
$ grep pong output | wc -l
404777
$ cat x.go 
package main
import (
    &quot;fmt&quot;
    &quot;time&quot;
)

type Ball struct{ hits int }

func main() {
    table := make(chan *Ball)
    go player(&quot;ping&quot;, table)
    go player(&quot;pong&quot;, table)

    table &lt;- new(Ball) // game on; toss the ball
    time.Sleep(1 * time.Second)
    &lt;-table // game over; grab the ball
}

func player(name string, table chan *Ball) {
    for {
        ball := &lt;-table
        ball.hits++
        fmt.Println(name, ball.hits)
        //time.Sleep(1 * time.Millisecond)
        table &lt;- ball
    }
}

However long you set the timeout in the player function (or remove it all together) you always get #ping == #ping +/- 1.

答案1

得分: 7

你正在使用一个无缓冲通道,并且你的两个 goroutine 通过它进行同步。使用无缓冲通道,通道的写入操作(table <- ball)只有在某个地方进行了读取操作(<-table)后才能完成,因此单个 goroutine 永远无法读取它自己写入的值。这就是这个示例的整个目的。

英文:

You're using a un-buffered channel and your two goroutines synchronize with it. With an un-buffered channel the channel write (table &lt;- ball) can only complete once someone somewhere has done a read (&lt;-table) so a single goroutine can never read the value it's writing. That's the whole point of this example.

答案2

得分: 1

根据这篇博客的说法:

对于任意数量的玩家,Goroutines会完美地交错执行:

答案是因为Go运行时维护了一个等待接收者的FIFO队列(准备在特定通道上接收的goroutines),而在我们的情况下,每个玩家在将球传递到桌子上后立即准备好了。

英文:

According to this blog:

Goroutines would interleave perfectly for any number of players:

> The answer is because Go runtime holds waiting FIFO queue for receivers (goroutines ready to receive on the particular channel), and
> in our case every player gets ready just after he passed the ball on
> the table

答案3

得分: -5

默认情况下,GOMAXPROCS被设置为1,因此您会看到这种行为。如果您增加GOMAXPROCS,它将不再是确定性的。

参见这个答案的示例

编辑@DaveC不同意,但简单的测试结果显示不同。通道是同步的,但是goroutine的执行顺序不是确定的。这些是不同的概念。在上面的代码中输入,设置GOMAXPROCS > 1并运行...

➜  tmp  export GOMAXPROCS=2
➜  tmp  go run balls.go
ping 1
pong 2
➜  tmp  go run balls.go
pong 1
ping 2
➜  tmp

如上所示,goroutine的执行顺序是不确定的。

英文:

By default GOMAXPROCS is set to 1 and as a result you see this behaviour. If you increase GOMAXPROCS it will no longer be deterministic.

See this answer for an example

EDIT @DaveC disagrees however a simple test shows otherwise. The channels are synchonised yes, however the order of the goroutines executing is not. These are different concepts. Type in the above code, set GOMAXPROCS > 1 and run ...

➜  tmp  export GOMAXPROCS=2
➜  tmp  go run balls.go
ping 1
pong 2
➜  tmp  go run balls.go
pong 1
ping 2
➜  tmp

As you can see above the goroutines order of execution is not deterministic

huangapple
  • 本文由 发表于 2015年3月16日 03:08:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/29064946.html
匿名

发表评论

匿名网友

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

确定