为什么这个程序终止于我的系统而不是在游乐场?

时间:2013-12-14 13:47:45

标签: concurrency go undefined-behavior

考虑这个程序:

package main

import "fmt"
import "time"
import "runtime"

func main() {

    x := 0

    go func() {
        time.Sleep(500 * time.Millisecond)
        x = 1
    }()

    for x == 0 {
        runtime.Gosched()
    }

    fmt.Println("it works!")
}

为什么它会在本地终止但不在Playground终止?我的程序终止是否依赖于未定义的行为?

3 个答案:

答案 0 :(得分:5)

Go游乐场使用time.Sleep的特殊实现,旨在防止个别程序垄断网站的后端资源。

this article about the how the playground is implemented中所述,调用time.Sleep()的goroutines将进入休眠状态。操场后端等待,直到所有其他goroutine被阻挡(否则会出现死锁),然后以最短的时间唤醒goroutine。

在你的程序中,你有两个goroutine:主要的一个,一个调用time.Sleep。由于主goroutine永远不会阻塞,time.Sleep调用永远不会返回。程序继续运行,直到它超过分配给它的CPU时间,然后终止。

答案 1 :(得分:4)

Go Memory Model不保证主程序可以观察到goroutine中写入x的值。在go routine destruction部分中给出了一个类似的错误程序作为示例。 Go Memory Model还专门用this section作为不正确的惯用语来调用繁忙的等待而没有同步。

你需要在goroutine中进行某种同步,以保证x = 1 发生在主循环中for循环的一次迭代之前。

这是一个保证按预期工作的程序版本。

http://play.golang.org/p/s3t5_-Q73W

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan bool)
    x := 0

    go func() {
        time.Sleep(500 * time.Millisecond)
        x = 1
        close(c) // 1
    }()

    for x == 0 {
        <-c // 2
    }

    fmt.Println("it works!")
}

Go Memory Model保证标记为// 1 的行发生在标记为// 2的行之前。因此,for循环保证在第二次迭代之前终止。 / p>

答案 2 :(得分:2)

该代码不能提供太多保证。它几乎完全依赖于未定义行为的实现细节。

在大多数多线程系统中,无法保证在另一个没有障碍的线程中发生变化。你有一个可以在另一个处理器上运行的goroutine,它将一个值写入一个无人能保证读取的变量。

for x == 0 {很容易被重写为for {,因为从来没有保证可以看到对该变量的任何更改。

竞赛检测器也可能会报告此问题。你真的不应该期待这个工作。如果你想要一个sync.WaitGroup,你应该只使用一个,因为它可以正确地跨线程协调。