Закрывает канал только отправитель, и только когда больше нечего отправлять. Получатель проверяет закрытие через второй возвращаемый параметр или range:
ch := make(chan int)
// отправитель
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
// получатель — range автоматически завершится при close
for v := range ch {
fmt.Println(v)
}
Нельзя писать в закрытый канал (panic), нельзя закрывать дважды (panic). Если несколько отправителей — используй отдельный done-канал или sync.Once. Канал не обязательно закрывать — GC соберёт его, когда на него нет ссылок.