web-dev-qa-db-ja.com

runtime.Goschedは正確に何をしますか?

Tour of Go Webサイトのgo 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()が削除されると、プログラムが「world」を出力しなくなることです。

_hello
hello
hello
hello
hello
_

どうしてこんなことに? runtime.Gosched()は実行にどのように影響しますか?

74
Jason Yeo

GOMAXPROCS環境変数を指定せずにGoプログラムを実行すると、Goゴルーチンは単一のOSスレッドで実行されるようにスケジュールされます。ただし、プログラムをマルチスレッドのように見せるために(それがgoroutineの目的ですよね?)、Goスケジューラーは時々実行コンテキストを切り替えなければならないため、各goroutineは作業を行うことができます。

先ほど述べたように、GOMAXPROCS変数が指定されていない場合、Goランタイムは1つのスレッドしか使用できないため、goroutineが計算やIO =(プレーンなC関数にマッピングされます。)Go同時実行プリミティブが使用されている場合のみコンテキストを切り替えることができます。たとえば、複数のchanをオンにした場合、または(これが当てはまります)これが_runtime.Gosched_の目的です。

つまり、1つのゴルーチンの実行コンテキストがGosched呼び出しに到達すると、スケジューラは実行を別のゴルーチンに切り替えるように指示されます。あなたの場合、2つのgoroutine、main(プログラムの「メイン」スレッドを表す)と追加、_go say_で作成したものがあります。 Gosched呼び出しを削除すると、実行コンテキストが最初のゴルーチンから2番目のゴルーチンに転送されることはないため、「ワールド」はありません。 Goschedが存在する場合、スケジューラーは各ループ反復の実行を最初のgoroutineから2番目のgoroutineに、またその逆に転送するため、「hello」と「world」がインターリーブされます。

参考までに、これは「協調的マルチタスク」と呼ばれます。ゴルーチンは他のゴルーチンに明示的に制御を委ねる必要があります。現在のほとんどのOSで使用されているアプローチは、「プリエンプティブマルチタスク」と呼ばれます。実行スレッドは、コントロールの転送に関係しません。スケジューラは、代わりに実行コンテキストを透過的に切り替えます。協調的アプローチは、「グリーンスレッド」、つまりOSスレッドに1:1でマッピングされない論理的並行コルーチンを実装するために頻繁に使用されます。これがGoランタイムとそのゴルーチンの実装方法です。

更新

GOMAXPROCS環境変数について言及しましたが、それが何であるかを説明しませんでした。これを修正する時が来ました。

この変数が正の数Nに設定されている場合、Goランタイムは最大Nネイティブスレッドを作成でき、その上ですべてのグリーンスレッドがスケジュールされます。ネイティブスレッドオペレーティングシステムによって作成されるスレッドの一種(Windowsスレッド、pthreadなど)。これは、Nが1より大きい場合、ゴルーチンが異なるネイティブスレッドで実行されるようにスケジュールされ、その結果、並列に実行される可能性があることを意味します(少なくとも、コンピューターの能力まで:システムがマルチコアプロセッサに基づいているため、これらのスレッドは完全に並列である可能性があります。プロセッサにシングルコアがある場合、OSスレッドに実装されたプリエンプティブマルチタスクにより、並列実行の可視性が作成されます)。

環境変数を事前設定する代わりに、runtime.GOMAXPROCS()関数を使用してGOMAXPROCS変数を設定することができます。現在のmainの代わりに、プログラムで次のようなものを使用します。

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

この場合、興味深い結果を観察できます。 「hello」と「world」の行が不均等に交互に印刷される可能性があります。

_hello
hello
world
hello
world
world
...
_

これは、ゴルーチンが別々の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ランタイムでは、ゴルーチンは、同時実行プリミティブの使用だけでなく、OSシステムコールでも生成するように強制します。これはIO関数呼び出しでもゴルーチン間で実行コンテキストを切り替えることができることを意味します。その結果、最近のGoコンパイラではGOMAXPROCSが未設定または1に設定されている場合でも不確定な動作を観察することができます。

129

協調スケジューリングが原因です。譲歩せずに、他の(たとえば「世界」)ゴルーチンは、メインの終了前/終了時に実行する機会が法的にゼロになる可能性があります。プロセス全体。

5
zzzz