runtime.Gosched到底做了什么?

在Tour 1.5网站发布之前的版本中 ,有一段代码看起来像这样。

package main import ( "fmt" "runtime" ) func say(s string) { for i := 0; i < 5; i++ { runtime.Gosched() fmt.Println(s) } } func main() { go say("world") say("hello") } 

输出如下所示:

 hello world hello world hello world hello world hello 

什么是困扰我的是,当runtime.Gosched()被删除,程序不再打印“世界”。

 hello hello hello hello hello 

为什么? runtime.Gosched()如何影响执行?

在不指定GOMAXPROCS环境variables的情况下运行Go程序时,Go例程将被安排在单个OS线程中执行。 但是,为了让程序看起来像是multithreading的(这就是goroutines的用途,不是吗?),Go调度器有时必须切换执行上下文,因此每个goroutine都可以完成它的工作。

正如我所说的,当没有指定GOMAXPROCSvariables时,Go运行时只允许使用一个线程,所以当goroutine执行一些常规工作(如计算甚至IO(映射到纯C函数)时不可能切换执行上下文)。 只有在使用Go并发原语时,例如当你打开几个chans,或者(当你明确地告诉调度器切换上下文时,这是你的情况),上下文才能被切换 – 这就是runtime.Gosched目的。

所以,简而言之,当一个goroutine中的执行上下文达到Gosched调用时,调度器被指示将执行切换到另一个goroutine。 在你的情况下,有两个例程,main(它代表程序的'main'线程)和另外一个,你创build的那个go say 。 如果你删除Gosched调用,执行上下文永远不会从第一个goroutine传输到第二个goroutine,因此对你来说没有“世界”。 当存在Gosched ,调度程序将执行的每个循环迭代从第一个goroutine转移到第二个goroutine,反之亦然,所以您有'hello'和'world'交错。

仅供参考,这被称为“合作多任务”:goroutines必须明确地将控制权交给其他goutoutines。 在大多数现代操作系统中使用的方法被称为“抢先式多任务”:执行线程不关心控制转移; 调度程序将执行上下文透明地切换到它们。 经常使用合作的方法来实现“绿色线程”,也就是逻辑并发协程,它们不会映射1:1到操作系统线程 – 这就是Go运行时及其例程的实现方式。

更新

我提到了GOMAXPROCS环境variables,但没有解释它是什么。 现在是解决这个问题的时候了。

当此variables设置为正数N ,Go运行时将能够创build最多N原生线程,在这些线程上将调度所有绿色线程。 本机线程是由操作系统(Windows线程,pthreads等)创build的一种线程。 这意味着如果N大于1,那么goroutines可能会计划在不同的本地线程中执行,因此可以并行运行(至less取决于您的计算机function:如果您的系统基于多核处理器,这些线程很可能是真正的并行;如果你的处理器具有单核,那么在OS线程中实现的抢先式多任务将创build并行执行的可视性。

可以使用runtime.GOMAXPROCS()函数来设置GOMAXPROCSvariables,而不是预先设置环境variables。 在你的程序中使用类似这样的东西来代替当前的main

 func main() { runtime.GOMAXPROCS(2) go say("world") say("hello") } 

在这种情况下,您可以观察有趣的结果。 你可能会得到“你好”和“世界”印刷不均匀交错,例如

 hello hello world hello world world ... 

如果goroutine计划分离操作系统线程,就会发生这种情况。 事实上,抢先式多任务处理(或多核系统中的并行处理)如下:线程是并行的,它们的组合输出是不确定的。 顺便说一句,你可以离开或移除Gosched呼叫,当GOMAXPROCS大于1时似乎没有效果。

以下是我用runtime.GOMAXPROCS调用几次运行的程序。

 hyperplex /tmp % go run test.go hello hello hello world hello world hello world hyperplex /tmp % go run test.go hello world hello world hello world hello world hello world hyperplex /tmp % go run test.go hello hello hello hello hello hyperplex /tmp % go run test.go hello world hello world hello world hello world hello world 

看,有时输出很漂亮,有时候不是。 不确定性在行动:)

另一个更新

看起来在Go编译器的新版本中,Go运行时强制goroutine不仅产生并发基元的使用,而且也产生OS系统调用。 这意味着执行上下文可以在IO函数调用之间在goroutine之间切换。 因此,在最近的Go编译器中,即使GOMAXPROCS未设置或设置为1,也可能观察到不确定行为。

合作计划是罪魁祸首。 没有屈服,另一个(也就是说“世界”)门厅在主要终止之前/之中可以合法地得到零的机会,每个规格终止所有的gorutine – 即。 整个过程。