runtime.Gosched究竟做什么? [英] What exactly does runtime.Gosched do?

查看:667
本文介绍了runtime.Gosched究竟做什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Go of Go go网站发布前的版本中,有一段代码看起来像这样。

  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

world
hello
world
hello

正在打扰我的是,当 runtime.Gosched()被删除,程序不再打印世界。

  hello 
hello
hello
hello
hello

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

解决方案

你运行Go程序而不指定GOMAXPROCS环境变量,Go goroutines被安排在单个OS线程中执行。然而,为了使程序看起来是多线程的(这是goroutines是什么,不是吗?),Go调度程序必须有时切换执行上下文,所以每个goroutine可以做它的工作。



正如我所说,当未指定GOMAXPROCS变量时,Go运行时只允许使用一个线程,因此在goroutine执行一些常规工作(如计算或甚至IO)时不可能切换执行上下文(其被映射到简单C函数)。仅当使用Go并发原语时,才可以切换上下文,例如。当你打开几个chans,或者(这是你的情况下)当你明确告诉调度程序切换上下文 - 这是 runtime.Gosched 是。



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

FYI,这被称为协作多任务:goroutines必须显式地产生对其他goroutine的控制。在大多数现代OS中使用的方法被称为抢先多任务:执行线程不关心控制转移;调度程序将执行上下文透明地切换到它们。合作方法经常用于实现'绿色线程',即逻辑并发协同,它不映射1:1到操作系统线程 - 这是Go运行时及其goroutine如何实现。



更新



我已经提到了GOMAXPROCS环境变量,但没有解释它是什么。



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



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

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

在这种情况下,您可以观察到有趣的结果。有可能你会得到hello和世界线打印不均匀,例如

  hello 
hello
world
hello
world
world
...


b $ b

这可能发生,如果goroutines被安排为分离操作系统线程。这实际上是多抢占多任务如何工作(或在多核系统的情况下的并行处理):线程是并行的,并且它们的组合输出是不确定的。 BTW,你可以离开或删除 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系统调用。这意味着执行上下文可以在goroutine之间也在IO函数调用之间切换。因此,在最近的Go编译器中,即使未设置GOMAXPROCS或将其设置为1,也可能观察到不确定的行为。


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")
}

The output looks like this:

hello
world
hello
world
hello
world
hello
world
hello

What is bothering me is that when runtime.Gosched() is removed, the program no longer prints "world".

hello
hello
hello
hello
hello

Why is that so? How does runtime.Gosched() affect the execution?

解决方案

When you run Go program without specifying GOMAXPROCS environment variable, Go goroutines are scheduled for execution in single OS thread. However, to make program appear to be multithreaded (that's what goroutines are for, aren't they?), the Go scheduler must sometimes switch the execution context, so each goroutine could do its piece of work.

As I said, when GOMAXPROCS variable is not specified, Go runtime is only allowed to use one thread, so it is impossible to switch execution contexts while goroutine is performing some conventional work, like computations or even IO (which is mapped to plain C functions). The context can be switched only when Go concurrency primitives are used, e.g. when you switch on several chans, or (this is your case) when you explicitly tell the scheduler to switch the contexts - this is what runtime.Gosched is for.

So, in short, when execution context in one goroutine reaches Gosched call, the scheduler is instructed to switch the execution to another goroutine. In your case there are two goroutines, main (which represents 'main' thread of the program) and additional, the one you have created with go say. If you remove Gosched call, the execution context will never be transferred from the first goroutine to the second, hence no 'world' for you. When Gosched is present, the scheduler transfers the execution on each loop iteration from first goroutine to the second and vice versa, so you have 'hello' and 'world' interleaved.

FYI, this is called 'cooperative multitasking': goroutines must explicitly yield the control to other goroutines. The approach used in most contemporary OSes is called 'preemptive multitasking': execution threads are not concerned with control transferring; the scheduler switches execution contexts transparently to them instead. Cooperative approach is frequently used to implement 'green threads', that is, logical concurrent coroutines which do not map 1:1 to OS threads - this is how Go runtime and its goroutines are implemented.

Update

I've mentioned GOMAXPROCS environment variable but didn't explain what is it. It's time to fix this.

When this variable is set to a positive number N, Go runtime will be able to create up to N native threads, on which all green threads will be scheduled. Native thread a kind of thread which is created by the operating system (Windows threads, pthreads etc). This means that if N is greater than 1, it is possible that goroutines will be scheduled to execute in different native threads and, consequently, run in parallel (at least, up to your computer capabilities: if your system is based on multicore processor, it is likely that these threads will be truly parallel; if your processor has single core, then preemptive multitasking implemented in OS threads will create a visibility of parallel execution).

It is possible to set GOMAXPROCS variable using runtime.GOMAXPROCS() function instead of pre-setting the environment variable. Use something like this in your program instead of the current main:

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

In this case you can observe interesting results. It is possible that you will get 'hello' and 'world' lines printed interleaved unevenly, e.g.

hello
hello
world
hello
world
world
...

This can happen if goroutines are scheduled to separate OS threads. This is in fact how preemptive multitasking works (or parallel processing in case of multicore systems): threads are parallel, and their combined output is indeterministic. BTW, you can leave or remove Gosched call, it seems to have no effect when GOMAXPROCS is bigger than 1.

The following is what I got on several runs of the program with runtime.GOMAXPROCS call.

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

See, sometimes output is pretty, sometimes not. Indeterminism in action :)

Another update

Looks like that in newer versions of Go compiler Go runtime forces goroutines to yield not only on concurrency primitives usage, but on OS system calls too. This means that execution context can be switched between goroutines also on IO functions calls. Consequently, in recent Go compilers it is possible to observe indeterministic behavior even when GOMAXPROCS is unset or set to 1.

这篇关于runtime.Gosched究竟做什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆