Напиши программу с тремя goroutines для генерации и вывода чисел без потерь и дедлоков в Go
Что проверяют
Умеете ли вы строить конвейер на каналах и понимаете ли, кто и когда закрывает канал. Задача не про алгоритм, а про дисциплину владения каналом: любая потеря чисел или зависание тут следствие нарушенного правила, а не случайности.
Как отвечать
Разложите работу на три стадии, по горутине на каждую, и соедините их каналами. Правило одно: канал закрывает тот, кто в него пишет, и только он.
func main() {
nums := make(chan int)
squares := make(chan int)
done := make(chan struct{})
go func() {
defer close(nums)
for i := 1; i <= 10; i++ {
nums <- i
}
}()
go func() {
defer close(squares)
for n := range nums {
squares <- n * n
}
}()
go func() {
defer close(done)
for n := range squares {
fmt.Println(n)
}
}()
<-done
}Потерь нет, потому что range читает канал до закрытия и забирает всё, что в него положили, а не последнее значение. Дедлока нет, потому что каждая стадия закрывает свой выход через defer, и цепочка закрытий доезжает до печати. Ожидание обязательно: main это тоже горутина, её выход убивает программу целиком. Вместо канала done подойдёт sync.WaitGroup, суть та же. Проверяется такой код запуском с флагом -race.
Где спотыкаются
Закрывают канал на стороне читателя или закрывают дважды. Второе закрытие это паника close of closed channel, запись в закрытый канал тоже паникует. Отсюда и правило про единственного владельца записи.
Забывают close вовсе. Тогда range на приёмнике ждёт вечно, и рантайм печатает fatal error: all goroutines are asleep - deadlock!, если работать больше некому.
Третье: main никого не ждёт и завершается первым, вывод получается обрезанным или пустым. И wg.Add нужно звать до запуска горутины, иначе Wait проскочит мимо ещё не учтённой.