Подтвердить что ты не робот

Выключение выражения 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?

4b9b3361

Ответ 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()

Я знаю, что это не единственный цикл выбора, но в этом случае я чувствую, что это более читабельно без условия "если".