In a version prior to the release of go 1.5 of the Tour of Go website, there's a piece of code that looks like this.
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")
}
Run Code Online (Sandbox Code Playgroud)
The output looks like this:
hello
world
hello
world
hello
world
hello
world
hello
Run Code Online (Sandbox Code Playgroud)
What is bothering me is that when runtime.Gosched()
is removed, the program no longer prints "world".
hello
hello
hello
hello
hello
Run Code Online (Sandbox Code Playgroud)
Why is that so? How does runtime.Gosched()
affect the execution?
Vla*_*eev 131
当您在不指定GOMAXPROCS环境变量的情况下运行Go程序时,将调度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")
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,您可以观察到有趣的结果 您可能会得到"hello"和"世界"线条不均匀地交错打印,例如
hello
hello
world
hello
world
world
...
Run Code Online (Sandbox Code Playgroud)
如果计划将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
Run Code Online (Sandbox Code Playgroud)
看,有时输出很漂亮,有时候不是.行动中的不确定性:)
另一个更新
看起来在较新版本的Go编译器Go运行时强制goroutines不仅要产生并发原语使用,还要产生OS系统调用.这意味着可以在IO函数调用上的goroutine之间切换执行上下文.因此,在最近的Go编译器中,即使GOMAXPROCS未设置或设置为1,也可以观察到不确定的行为.
合作调度是罪魁祸首.在没有屈服的情况下,另一个(比如说"世界")goroutine可能在主要终止之前/当主要终止时合法地获得零机会,其中每个规格终止所有gorutines - 即.整个过程.