Как правильно читать значения из канала и выйти по закрытию канала в Go?
Что проверяют
Знаете ли вы обе формы чтения и понимаете ли поведение закрытого канала в select.
Как отвечать
Два способа.
1. Через range. Цикл сам завершится, когда канал закроют и буфер опустеет:
for v := range ch {
process(v)
}2. С проверкой второго значения. Нужен, когда чтение идёт не в цикле или требуется различать закрытие:
v, ok := <-ch
if !ok {
// канал закрыт и пуст
}Правила про закрытие:
- закрывает только отправитель, и только один. Получатель закрывать не должен, потому что не знает, будут ли ещё данные;
- закрывать не обязательно: канал соберётся сборщиком мусора. Закрытие это сигнал «данных больше не будет», нужный получателям;
- повторное закрытие и отправка в закрытый канал вызывают панику;
- чтение из закрытого возвращает нулевое значение и
falseнемедленно.
Отдельно про select, потому что там есть ловушка. Закрытый канал всегда готов к чтению, поэтому ветка с ним будет срабатывать бесконечно, и цикл начнёт крутиться вхолостую. Приём: обнулить переменную канала, потому что чтение из нулевого канала блокируется навсегда, и ветка перестаёт выбираться:
for ch != nil || other != nil {
select {
case v, ok := <-ch:
if !ok { ch = nil; continue }
use(v)
case <-ctx.Done():
return ctx.Err()
}
}Про нескольких отправителей: закрывать канал из каждого нельзя. Схема такая: все отправители отмечаются в группе ожидания, а отдельная горутина ждёт её завершения и только потом закрывает канал.
Где спотыкаются
Закрывают канал со стороны получателя.
Закрывают канал из нескольких отправителей и ловят панику.
Не обнуляют канал в select и получают холостой цикл.
Считают, что закрытие обязательно для освобождения памяти.