Выключение выражения select при закрытии всех каналов
У меня есть два goroutines независимо от данных, каждый отправляет их на канал. В моем главном goroutine я хотел бы потреблять каждый из этих выходов по мере их поступления, но не волнует порядок, в котором они входят. Каждый канал закроется, когда он исчерпает свой выход. Хотя оператор 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}
}
Но похоже, что это получится несостоятельным, если вы работаете с более чем двумя или тремя каналами. Единственным другим способом, который я знаю, является использование случая тайм-аута в операторе switch, который будет либо достаточно мал, чтобы рискнуть рано, либо ввести слишком много времени простоя в конечный цикл. Есть ли лучший способ проверить, что каналы находятся в выражении select?
Ответы
Ответ 1
Ваше примерное решение не будет работать. Как только один из них будет закрыт, он всегда будет доступен для связи сразу. Это означает, что ваш горутин никогда не выйдет, а другие каналы никогда не будут готовы. Вы эффективно входите в бесконечный цикл. Я привел пример для иллюстрации эффекта здесь: http://play.golang.org/p/rOjdvnji49
Итак, как я могу решить эту проблему? Ниль-канал никогда не готов к общению. Таким образом, каждый раз, когда вы запускаете закрытый канал, вы можете использовать этот канал, чтобы он никогда не выбирался снова. Runable example здесь: 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
}
}
Что касается того, что он боится стать громоздким, я не думаю, что так будет. Очень редко у вас есть каналы, идущие в слишком много мест одновременно. Это было бы так редко, что мое первое предложение - просто разобраться с этим. Длинный оператор if, сравнивающий 10 каналов с nil, не является наихудшей частью попытки иметь дело с 10 каналами в select.
Ответ 2
Закрыть в некоторых ситуациях приятно, но не все. Я бы не использовал его здесь. Вместо этого я просто использовал бы готовый канал:
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
Ответ 3
Почему бы не использовать goroutines? Когда ваши каналы закрываются, все это превращается в простой цикл диапазона.
func foo(c chan whatever, prefix s) {
for v := range c {
fmt.Println(prefix, v)
}
}
// ...
go foo(mins, "Min:")
go foo(maxs, "Max:")
Ответ 4
Я написал пакет, который предоставляет функцию для решения этой проблемы (среди нескольких других):
https://github.com/eapache/channels
https://godoc.org/github.com/eapache/channels
Проверьте функцию Multiplex
. Он использует отражение для масштабирования для произвольного количества входных каналов.
Ответ 5
Когда я столкнулся с такой необходимостью, я выбрал следующий подход:
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for p := range mins {
fmt.Println("Min:", p)
}
}()
go func() {
defer wg.Done()
for p := range maxs {
fmt.Println("Max:", p)
}
}()
wg.Wait()
Я знаю, что это не единственный цикл выбора, но в этом случае я чувствую, что это более читабельно без условия "если".