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 – 即。 整个过程。