12

次のプログラムを書きました。

package main

import (
    "fmt"
)

func processevents(list chan func()) {
    for {
        //a := <-list
        //a()
    }
}

func test() {
    fmt.Println("Ho!")
}

func main() {

    eventlist := make(chan func(), 100)

    go processevents(eventlist)

    for {
        eventlist <- test
        fmt.Println("Hey!")
    }
}

チャネルのイベントリストはバッファリングされたチャネルなので、出力「Hey!」のちょうど 100 倍になるはずですが、表示されるのは 1 回だけです。私の間違いはどこですか?

4

3 に答える 3

23

更新 (Go バージョン 1.2+)

Go 1.2 の時点で、スケジューラはプリエンプティブ マルチタスクの原則に基づいて動作します。これは、元の質問の問題 (および以下に示す解決策) がもはや関連していないことを意味します。

Go 1.2 リリースノートより

スケジューラーでのプリエンプション

以前のリリースでは、永久にループしていたゴルーチンが同じスレッド上の他のゴルーチンを枯渇させる可能性がありました。これは、GOMAXPROCS が 1 つのユーザー スレッドしか提供しなかった場合に深刻な問題でした。Go > 1.2 では、これは部分的に対処されています。スケジューラは、関数へのエントリ時に時々呼び出されます。これは、(インライン化されていない) 関数呼び出しを含むループを横取りして、他のゴルーチンを同じスレッドで実行できることを意味します。

簡潔な答え

書き込みをブロックしていません。の無限ループに陥っていますprocessevents。このループはスケジューラーに渡されることはなく、すべてのゴルーチンが無期限にロックされます。

への呼び出しをコメント アウトするとprocessevents、100 回目の書き込みまで、期待どおりの結果が得られます。その時点で、誰もチャネルから読み取らないため、プログラムはパニックになります。

runtime.Gosched()別の解決策は、呼び出しをループに入れることです。

長い答え

Go1.0.2 では、Go のスケジューラはCooperative multitaskingの原則に基づいて動作します。これは、特定の OS スレッド内で実行されているさまざまなゴルーチンに CPU 時間を割り当てることを意味します。これらのルーチンは、特定の条件でスケジューラと対話します。これらの「相互作用」は、特定のタイプのコードがゴルーチンで実行されるときに発生します。go の場合、これには何らかの I/O、システムコール、またはメモリ割り当て (特定の条件で) が含まれます。

空のループの場合、そのような条件は発生しません。したがって、ループが実行されている限り、スケジューラはスケジューリング アルゴリズムを実行できません。その結果、実行を待機している他の goroutine に CPU 時間を割り当てることができなくなり、結果は次のようになります。つまり、スケジューラーによって検出または解除できないデッドロックが効果的に作成されました。

空のループは通常、Go では望ましくなく、ほとんどの場合、プログラムのバグを示します。なんらかの理由でそれが必要な場合はruntime.Gosched()、すべての反復で呼び出して手動でスケジューラーに譲らなければなりません。

for {
    runtime.Gosched()
}

GOMAXPROCS値への設定> 1が解決策として言及されました。これにより、観察した差し迫った問題が取り除かれますが、スケジューラーがループするゴルーチンを独自の OS スレッドに移動することを決定した場合、問題は効果的に別の OS スレッドに移動します。runtime.LockOSThread()関数の開始時に呼び出さない限り、これは保証されませんprocessevents。それでも、私はこのアプローチが良い解決策であることにまだ依存していません。runtime.Gosched()goroutine が実行されている OS スレッドに関係なく、ループ自体を呼び出すだけですべての問題が解決します。

于 2012-09-13T19:41:53.917 に答える
9

別の解決策があります-rangeチャネルから読み取るために使用します。このコードはスケジューラーに正しく譲り、チャネルが閉じられると適切に終了します。

func processevents(list chan func()) {
    for a := range list{
        a()
    }
}
于 2012-09-14T07:02:50.007 に答える