99

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()を削除すると、プログラムが「世界」を出力しなくなることです。

hello
hello
hello
hello
hello

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

4

2 に答える 2

163

ノート:

Go 1.5 の時点で、GOMAXPROCS はハードウェアのコア数に設定されています: golang.org/doc/go1.5#runtime、1.5 より前の元の回答の下。


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

前述したように、GOMAXPROCS 変数が指定されていない場合、Go ランタイムは 1 つのスレッドしか使用できないため、ゴルーチンが計算や IO (プレーンな C 関数にマップされる) などの従来の作業を実行している間は、実行コンテキストを切り替えることはできません。 )。コンテキストを切り替えることができるのは、Go 同時実行プリミティブが使用されている場合、たとえば、複数のチャンをオンにする場合、または (これはあなたの場合です) スケジューラにコンテキストを切り替えるように明示的に指示する場合です。これruntime.Goschedが目的です。

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

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

アップデート

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

この変数が正の数に設定されている場合、Go ランタイムは、すべてのグリーン スレッドがスケジュールされるネイティブ スレッドNまで作成できます。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以下は、 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

ほら、出力がきれいな場合もあれば、そうでない場合もあります。行動中の不確定性:)

別のアップデート

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

于 2012-10-28T11:37:00.640 に答える
10

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

于 2012-10-28T11:19:12.593 に答える