Go Playground和我的机器上的Go之间的差异?

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

Discrepancies between Go Playground and Go on my machine?

问题

为了解决我对goroutine的一些误解,我去了Go Playground并运行了这段代码:

package main

import (
	"fmt"
)

func other(done chan bool) {
	done <- true
	go func() {
		for {
			fmt.Println("Here")
		}
	}()
}

func main() {
	fmt.Println("Hello, playground")
	done := make(chan bool)
	go other(done)
	<-done
	fmt.Println("Finished.")
}

正如我所预料的,Go Playground返回了一个错误:Process took too long

这似乎意味着在other函数中创建的goroutine会一直运行。

但是当我在自己的机器上运行相同的代码时,几乎瞬间就得到了以下输出:

Hello, playground.
Finished.

这似乎意味着在other函数中的goroutine在主goroutine完成时退出。这是真的吗?还是主goroutine完成后,其他goroutine继续在后台运行?

英文:

To settle some misunderstandings I have about goroutines, I went to the Go playground and ran this code:

package main

import (
    &quot;fmt&quot;
)

func other(done chan bool) {
    done &lt;- true
    go func() {
        for {
            fmt.Println(&quot;Here&quot;)
        }
    }()
}

func main() {
    fmt.Println(&quot;Hello, playground&quot;)
    done := make(chan bool)
    go other(done)
    &lt;-done
    fmt.Println(&quot;Finished.&quot;)
}

As I expected, Go playground came back with an error: Process took too long.

This seems to imply that the goroutine created within other runs forever.

But when I run the same code on my own machine, I get this output almost instantaneously:

Hello, playground.
Finished.

This seems to imply that the goroutine within other exits when the main goroutine finishes. Is this true? Or does the main goroutine finish, while the other goroutine continues to run in the background?
1: https://play.golang.org/p/2oEy6GKPDu

答案1

得分: 4

**编辑:**Go Playground上的默认GOMAXPROCS已更改,现在默认为8。在“旧”版本中,默认为1。要获得问题中描述的行为,请使用runtime.GOMAXPROCS(1)将其显式设置为1。

解释所见内容:

在Go Playground上,GOMAXPROCS是1(证明见此处)。

这意味着一次只执行一个goroutine,如果该goroutine不阻塞,调度器就不会强制切换到其他goroutine。

你的代码(像每个Go应用程序一样)从执行main()函数的goroutine开始(主goroutine)。它启动另一个执行other()函数的goroutine,然后从done通道接收数据-这会阻塞。因此,调度器必须切换到另一个goroutine(执行other()函数)。

在你的other()函数中,当你在done通道上发送一个值时,当前的other()main goroutine都变为可运行状态。调度器选择继续运行other(),并且由于GOMAXPROCS=1main()不会继续运行。现在,other()启动另一个执行无限循环的goroutine。调度器选择执行此goroutine,该goroutine需要很长时间才能进入阻塞状态,因此main()不会继续运行。

然后,Go Playground的沙箱超时作为绝对条件出现:

进程运行时间过长

请注意,Go内存模型只保证某些事件在其他事件之前发生,你无法保证两个并发的goroutine如何执行。这使得输出是不确定的。

你不能质疑不违反Go内存模型的任何执行顺序。如果你希望执行到代码中的某些点(执行某些语句),你需要显式同步(需要同步你的goroutine)。

还要注意,Go Playground上的输出是被缓存的,因此如果你再次运行该应用程序,它不会重新运行,而是立即呈现缓存的输出。如果你更改代码中的任何内容(例如插入一个空格或注释),然后再次运行它,它将被编译和重新运行。你会注意到响应时间增加。但是,使用当前版本(Go 1.6),你每次都会看到相同的输出。

在本地运行(在你的机器上):

当你在本地运行它时,GOMAXPROCS很可能大于1,因为它默认为可用的CPU核心数(自Go 1.5起)。因此,如果你有一个执行无限循环的goroutine,另一个goroutine将同时执行,这将是main(),当main()返回时,你的程序终止;它不会等待其他非main goroutine 完成(参见规范:程序执行)。

还要注意,即使你将GOMAXPROCS设置为1,你的应用程序很可能会在“短”时间内退出,因为调度器实现将切换到其他goroutine,而不仅仅是永远执行无限循环(但是,如上所述,这是不确定的)。当它这样做时,它将是main() goroutine,所以当main()完成并返回时,你的应用程序终止。

在Go Playground上运行你的应用程序:

如前所述,默认情况下,Go Playground上的GOMAXPROCS为1。但是允许将其设置为更高的值,例如:

runtime.GOMAXPROCS(2)

在没有显式同步的情况下,执行仍然是不确定的,但你将观察到不同的执行顺序,并且不会遇到超时而终止:

Hello, playground
Here
Here
Here
...
<Here is printed 996 times, then:>
Finished.

Go Playground上尝试这个变体。

英文:

Edit: Default GOMAXPROCS has changed on the Go Playground, it now defaults to 8. In the "old" days it defaulted to 1. To get the behavior described in the question, set it to 1 explicitly with runtime.GOMAXPROCS(1).

Explanation of what you see:

On the Go Playground, GOMAXPROCS is 1 (proof).

This means one goroutine is executed at a time, and if that goroutine does not block, the scheduler is not forced to switch to other goroutines.

Your code (like every Go app) starts with a goroutine executing the main() function (the main goroutine). It starts another goroutine that executes the other() function, then it receives from the done channel - which blocks. So the scheduler must switch to the other goroutine (executing other() function).

In your other() function when you send a value on the done channel, that makes both the current (other()) and the main goroutine runnable. The scheduler chooses to continue to run other(), and since GOMAXPROCS=1, main() is not continued. Now other() launches another goroutine executing an endless loop. The scheduler chooses to execute this goroutine which takes forever to get to a blocked state, so main() is not continued.

And then the timeout of the Go Playground's sandbox comes as an absolution:

> process took too long

Note that the Go Memory Model only guarantees that certain events happen before other events, you have no guarantee how 2 concurrent goroutines are executed. Which makes the output non-deterministic.

You are not to question any execution order that does not violate the Go Memory Model. If you want the execution to reach certain points in your code (to execute certain statements), you need explicit synchronization (you need to synchronize your goroutines).

Also note that the output on the Go Playground is cached, so if you run the app again, it won't be run again, but instead the cached output will be presented immediately. If you change anything in the code (e.g. insert a space or a comment) and then you run it again, it then will be compiled and run again. You will notice it by the increased response time. Using the current version (Go 1.6) you will see the same output every time though.

Running locally (on your machine):

When you run it locally, most likely GOMAXPROCS will be greater than 1 as it defaults to the number of CPU cores available (since Go 1.5). So it doesn't matter if you have a goroutine executing an endless loop, another goroutine will be executed simultaneously, which will be the main(), and when main() returns, your program terminates; it does not wait for other non-main goroutines to complete (see Spec: Program execution).

Also note that even if you set GOMAXPROCS to 1, your app will most likely exit in a "short" time as the scheduler imlementation will switch to other goroutines and not just execute the endless loop forever (however, as stated above, this is non-deterministic). And when it does, it will be the main() goroutine, and so when main() finishes and returns, your app terminates.

Playing with your app on the Go Playground:

As mentioned, by default GOMAXPROCS is 1 on the Go Playground. However it is allowed to set it to a higher value, e.g.:

runtime.GOMAXPROCS(2)

Without explicit synchronization, execution still remains non-deterministic, however you will observe a different execution order and a termination without running into a timeout:

Hello, playground
Here
Here
Here
...
&lt;Here is printed 996 times, then:&gt;
Finished.

Try this variant on the Go Playground.

答案2

得分: 0

你在屏幕上看到的是非确定性的。更准确地说,如果你传递给通道的true值被延迟,你会看到一些"Here"。

但通常情况下,标准输出是有缓冲的,这意味着它不会立即打印,而是数据会累积起来,当达到最大缓冲大小时才会打印出来。在你的情况下,在"here"被打印之前,主函数已经执行完毕,因此进程结束。

一个经验法则是:主函数必须保持活动状态,否则所有其他的goroutine都会被终止。

英文:

What you will see on screen is nondeterministic. Or more precisely if by any chance the true value you pass to channel is delayed you would see some "Here".

But usually the Stdout is buffered, it means it's not printed instantaneously but the data gets accumulated and after it gets to maximum buffer size it's printed. In your case before the "here" is printed the main function is already finished thus the process finishes.

The rule of thumb is: main function must be alive otherwise all other goroutines gets killed.

huangapple
  • 本文由 发表于 2016年4月19日 07:06:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/36705801.html
匿名

发表评论

匿名网友

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

确定