2012-10-28 232 views
56

a version prior to the release of go 1.5 of the Tour of Go website中,有一段代码看起来像这样。runtime.Gosched究竟做了什么?

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()如何影响执行?

回答

94

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

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

因此,简而言之,当一个例程中的执行上下文达到Gosched调用时,指示调度程序将执行切换到另一个goroutine。在你的情况下,有两个goroutines,main(它代表程序的'main'线程)和另外一个,你用go say创建的。如果删除Gosched调用,执行上下文将永远不会从第一个goroutine传输到第2个goroutine,因此对您而言没有“世界”。当存在Gosched时,调度程序会将每次循环迭代的执行从第一个goroutine转移到第二个goroutine,反之亦然,所以您有'hello'和'world'交错。我们称之为'合作式多任务':goroutines必须明确地将控制权交给其他goroutines。大多数现代操作系统使用的方法称为“抢先式多任务”:执行线程不关心控制传输;调度程序将执行上下文透明地切换给它们。经常使用合作的方法来实现'绿色线程',也就是逻辑并发协程,它们不会将1:1映射到操作系统线程 - 这就是Go运行时及其goroutines的实现方式。

更新

我已经提到GOMAXPROCS环境变量,但没有说明它是什么。现在是解决这个问题的时候了。

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

可以使用runtime.GOMAXPROCS()函数来设置GOMAXPROCS变量,而不是预先设置环境变量。使用这样的事情在你的程序,而不是当前main的:

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

在这种情况下,你可以看到有趣的结果。您可能会得到打印错误交错的“hello”和“world”行,例如

hello 
hello 
world 
hello 
world 
world 
... 

如果够程定于单独操作系统的线程会发生这种情况。实际上,抢先式多任务处理(或多核系统情况下的并行处理)如何实现:线程是并行的,并且它们的组合输出是不确定的。顺便说一句,你可以离开或删除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运行时力的新版本够程产生不仅并发原语的用法,但对OS的系统调用了。这意味着执行上下文可以在IO函数调用中在goroutine之间切换。因此,在最近的Go编译器中,即使当GOMAXPROCS未设置或设置为1时,也可以观察到不确定行为。

+0

伟大的工作!但我没有遇到这个问题下去1.0.3,更奇怪。 – MrROY

+1

这是真的。我刚刚检查了1.0.3版本,是的,这种行为没有出现:即使使用GOMAXPROCS == 1,程序的工作方式就好像GOMAXPROCS> = 2。似乎在1.0.3中调度器已经调整。 –

+0

谢谢!我希望我能两次赞扬这个! –

3

合作计划是罪魁祸首。如果没有屈服,另一个(比如说“世界”)的goroutine可能合法地在零/主要终止之前执行零机会,每个规格终止所有gorutine - 即。整个过程。

+1

好吧,运行时.Gosched()会产生。那是什么意思?它产生控制回到主要功能? –

+4

在这个特定的情况下是的。一般而言,它会要求调度程序以有意未明的选择顺序启动并运行任何一个“就绪”的goroutine。 – zzzz