73

私は2つのゴルーチンが独立してデータを生成し、それぞれがデータをチャネルに送信しています。私のメインのゴルーチンでは、これらの各出力を入力時に消費したいと思いますが、入力の順序は気にしないでください。各チャネルは、出力がなくなると自動的に閉じます。selectステートメントは、このように独立して入力を消費するための最も優れた構文ですが、両方のチャネルが閉じるまで、それぞれをループする簡潔な方法を見たことがありません。

for {
    select {
    case p, ok := <-mins:
        if ok {
            fmt.Println("Min:", p) //consume output
        }
    case p, ok := <-maxs:
        if ok {
            fmt.Println("Max:", p) //consume output
        }
    //default: //can't guarantee this won't happen while channels are open
    //    break //ideally I would leave the infinite loop
                //only when both channels are done
    }
}

私が考えることができる最善のことは次のとおりです(スケッチしたばかりで、コンパイルエラーがある可能性があります):

for {
    minDone, maxDone := false, false
    select {
    case p, ok := <-mins:
        if ok {
            fmt.Println("Min:", p) //consume output
        } else {
            minDone = true
        }
    case p, ok := <-maxs:
        if ok {
            fmt.Println("Max:", p) //consume output
        } else {
            maxDone = true
        }
    }
    if (minDone && maxDone) {break}
}

しかし、これは、2つまたは3つを超えるチャネルで作業している場合は受け入れられないように見えます。私が知っている他の唯一の方法は、switchステートメントでtimoutケースを使用することです。これは、早期に終了するリスクを冒すのに十分小さいか、最終ループに過度のダウンタイムを注入します。selectステートメント内にあるチャネルをテストするためのより良い方法はありますか?

4

5 に答える 5

135

あなたの例のソリューションはうまくいきません。それらのいずれかが閉じられると、いつでもすぐに通信できるようになります。これは、あなたのゴルーチンが絶対に譲歩せず、他のチャネルが準備できていない可能性があることを意味します。事実上、無限ループに陥ります。ここに効果を説明するための例を投稿しました: http://play.golang.org/p/rOjdvnji49

それで、どうすればこの問題を解決できますか?nil チャネルは通信の準備ができていません。したがって、閉じたチャネルに遭遇するたびに、そのチャネルを nil して、二度と選択されないようにすることができます。ここで実行可能な例: http://play.golang.org/p/8lkV_Hffyj

for {
    select {
    case x, ok := <-ch:
        fmt.Println("ch1", x, ok)
        if !ok {
            ch = nil
        }
    case x, ok := <-ch2:
        fmt.Println("ch2", x, ok)
        if !ok {
            ch2 = nil
        }
    }

    if ch == nil && ch2 == nil {
        break
    }
}

扱いにくくなるのが怖いということですが、そうはならないと思います。一度にあまりにも多くの場所にチャンネルを送信することは非常にまれです。これは非常にめったに発生しないため、私の最初の提案は、それに対処することです。10 チャネルを nil と比較する長い if ステートメントは、select で 10 チャネルを処理しようとする場合の最悪の部分ではありません。

于 2012-12-02T05:36:28.063 に答える
30

クローズは状況によっては便利ですが、すべてではありません。ここでは使いません。代わりに、done チャンネルを使用します。

for n := 2; n > 0; {
    select {
    case p := <-mins:
        fmt.Println("Min:", p)  //consume output
    case p := <-maxs:
        fmt.Println("Max:", p)  //consume output
    case <-done:
        n--
    }
}

プレイグラウンドでの完全な作業例: http://play.golang.org/p/Cqd3lg435y

于 2012-12-02T08:07:26.470 に答える
11

ゴルーチンを使用しないのはなぜですか? チャネルが閉じられると、全体が単純な範囲ループに変わります。

func foo(c chan whatever, prefix s) {
        for v := range c {
                fmt.Println(prefix, v)
        }
}

// ...

go foo(mins, "Min:")
go foo(maxs, "Max:")
于 2012-12-02T10:19:25.190 に答える
2

この問題を解決する機能を提供するパッケージを作成しました (他のいくつかの中でも):

https://github.com/eapache/channels

https://godoc.org/github.com/eapache/channels

機能をチェックしてくださいMultiplex。リフレクションを使用して、任意の数の入力チャネルにスケーリングします。

于 2014-01-15T15:33:43.460 に答える