1. ホーム
  2. go

全チャンネル閉鎖時のselect文からの脱却

2023-12-06 15:54:15

質問

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 文でタイムアウトのケースを使用することですが、これは早期に終了するリスクを冒すには十分小さいか、最終ループにあまりにも多くのダウンタイムを注入することになります。selectステートメント内にあるチャンネルをテストするためのより良い方法はありますか?

どのように解決するのですか?

あなたの例の解決策はうまくいきません。一旦それらのうちの 1 つが閉じられると、それは常にすぐに通信可能な状態になります。これは、あなたのゴルーチンが決して降伏せず、他のチャネルが決して準備できないことを意味します。事実上、無限ループに入ることになります。この効果を説明するために、ここに例を掲載しました。 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 個のチャンネルを処理しようとするときの最悪の部分ではありません。