英文:
Why does this program terminate on my system but not on playground?
问题
考虑以下程序:
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上却没有终止?我的程序的终止是否依赖于未定义的行为?
英文:
Consider this program:
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!")
}
Why does it terminate locally but not on Playground? Does the termination of my program rely on undefined behavior?
答案1
得分: 5
Go playground使用了一个特殊的time.Sleep
实现,旨在防止个别程序垄断网站的后端资源。
如这篇关于playground实现的文章所述,调用time.Sleep()的goroutine会被休眠。Playground后端会等待所有其他goroutine被阻塞(否则会发生死锁),然后唤醒具有最短超时时间的goroutine。
在你的程序中,有两个goroutine:主goroutine和一个调用time.Sleep
的goroutine。由于主goroutine从不阻塞,time.Sleep
调用将永远不会返回。程序会继续运行,直到超过分配给它的CPU时间,然后被终止。
英文:
The Go playground uses a special implementation of time.Sleep
designed to prevent individual programs from monopolising the back end resources of the website.
As described in this article about the how the playground is implemented, goroutines that call time.Sleep() are put to sleep. The playground back end waits until all other goroutines are blocked (what would otherwise be a deadlock), and then wakes the goroutine with the shortest time out.
In your program, you have two goroutines: the main one, and one that calls time.Sleep
. Since the main goroutine never blocks, the time.Sleep
call will never return. The program continues until it exceeds the CPU time allocated to it and is then terminated.
答案2
得分: 4
Go内存模型不能保证在goroutine中写入的值会被主程序观察到。在goroutine销毁的部分中,给出了一个类似错误的示例程序。Go内存模型还特别指出,在这个部分中,忙等待而没有同步的做法是不正确的。
为了保证在主程序的for循环的某个迭代之前发生x=1,你需要在goroutine中进行某种形式的同步。
下面是一个保证按预期工作的程序版本:
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内存模型保证了被标记为// 1的行在被标记为// 2的行之前发生。因此,这个for循环保证在第二次迭代之前终止。
英文:
The Go Memory Model does not guarantee that the value written to x in the goroutine will ever be observed by the main program. A similarly erroneous program is given as an example in the section on go routine destruction. The Go Memory Model also specifically calls out busy waiting without synchronization as an incorrect idiom in this section.
You need to do some kind of synchronization in the goroutine in order to guarantee that x=1 happens before one of the iterations of the for loop in main.
Here is a version of the program that is guaranteed to work as intended.
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!")
}
The Go Memory Model guarantees that the line marked with // 1 happens before the line marked with // 2. As a result, the for loop is guaranteed to terminate before its second iteration.
答案3
得分: 2
那段代码并没有提供太多的保证。它几乎完全依赖于未定义行为的实现细节。
在大多数多线程系统中,没有保证一个线程中的变化在没有屏障的情况下会被另一个线程看到。你有一个可能在另一个处理器上运行的 goroutine,它向一个没有人保证会读取的变量写入一个值。
for x == 0 {
可以很容易地重写为 for {
,因为没有保证任何对该变量的更改可能是可见的。
竞争检测器也可能会报告此问题。你真的不应该期望这样能工作。如果你想要一个 sync.WaitGroup
,你应该直接使用它,因为它可以在线程之间正确协调。
英文:
That code doesn't offer much guarantees. It's relying almost entirely on implementation details around undefined behavior.
In most multi-threaded systems, there's no guarantee that a change in one thread with no barrier will be seen in another. You've got a goroutine which could be running on another processor altogether writing a value to a variable nobody's ever guaranteed to read.
The for x == 0 {
could quite easily be rewritten to for {
since there's never a guarantee any changes to that variable might be visible.
The race detector will also probably report this issue. You should really not expect this to work. If you want a sync.WaitGroup
you should just use one as it properly coordinates across threads.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论