runtime.Gosched究竟做了什么?

时间:2012-10-28 10:12:51

标签: concurrency go

a version prior to the release of go 1.5 of the Tour of Go website中,有一段代码看起来像这样。

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

2 个答案:

答案 0 :(得分:123)

当您运行Go程序而未指定GOMAXPROCS环境变量时,Go goroutine计划在单个OS线程中执行。但是,为了使程序看起来是多线程的(这就是goroutine的用途,不是吗?),Go调度程序有时必须切换执行上下文,因此每个goroutine都可以完成它的工作。

正如我所说,当未指定GOMAXPROCS变量时,Go运行时只允许使用一个线程,因此当goroutine执行某些传统工作(如计算甚至IO(映射到)时)无法切换执行上下文普通C函数)。只有在使用Go并发原语时才能切换上下文,例如,当你明确告诉调度程序切换上下文时,当你打开几个chan时,或者(这是你的情况) - 这就是runtime.Gosched的用途。

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

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

<强>更新

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

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

可以使用runtime.GOMAXPROCS()函数设置GOMAXPROCS变量,而不是预先设置环境变量。在您的程序中使用类似的内容而不是当前的main

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

在这种情况下,您可以观察到有趣的结果。您可能会将“hello”和“world”行不均匀地交错打印,例如

hello
hello
world
hello
world
world
...

如果计划将goroutine分离为OS线程,则会发生这种情况。这实际上是抢占式多任务处理(或多核系统情况下的并行处理):线程是并行的,它们的组合输出是不确定的。顺便说一句,您可以离开或删除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运行时强制goroutines不仅会产生并发原语使用,而且还会产生OS系统调用。这意味着可以在IO函数调用上的goroutine之间切换执行上下文。因此,在最近的Go编译器中,即使GOMAXPROCS未设置或设置为1,也可以观察到不确定行为。

答案 1 :(得分:5)

合作调度是罪魁祸首。在没有屈服的情况下,另一个(比如说“世界”)goroutine可能在主要终止之前/当主要终止时合法地获得零机会,其中每个规格终止所有gorutines - 即。整个过程。