Что выведет код? Буферизованный канал
Условие
Определите, что выведет следующий код:
package main
import "fmt"
func main() {
ch := make(chan int, 4)
go func() {
ch <- 1
ch <- 2
ch <- 3
ch <- 4
ch <- 5
close(ch)
}()
for num := range ch {
fmt.Println(num)
}
}
Вопросы
- Что выведет программа?
- Будет ли deadlock? Почему?
- Что изменится, если убрать горутину?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Эта задача про буферизованные каналы и их поведение при отправке и получении данных. Ответ демонстрирует важное понимание concurrency в Go.
Ответ на вопросы
1. Что выведет программа?
1
2
3
4
5
Программа выведет все 5 чисел без ошибок и deadlock-а.
2. Будет ли deadlock? Почему?
НЕТ, deadlock-а НЕ будет, хотя могло бы быть в других сценариях.
Анализ работы буферизованного канала
Ключевое различие — буферизованный канал (make(chan int, 4)):
Безбуферный канал: make(chan int) — buffer size = 0
Буферизованный: make(chan int, 4) — buffer size = 4
Как это работает:
ch := make(chan int, 4) // буфер на 4 элемента
// Горутина может отправить до 4 элементов БЕЗ получателя
ch <- 1 // буфер: [1] (len=1, cap=4)
ch <- 2 // буфер: [1, 2] (len=2, cap=4)
ch <- 3 // буфер: [1, 2, 3] (len=3, cap=4)
ch <- 4 // буфер: [1,2,3,4] (len=4, cap=4) ПОЛНЫЙ
ch <- 5 // DEADLOCK! (нет места в буфере, нет получателя)
Почему в этом коде НЕТ deadlock-а?
Потому что горутина запущена в background:
go func() { // ← горутина в background
ch <- 1 // отправляет в канал
ch <- 2
ch <- 3
ch <- 4
ch <- 5 // 5-я отправка ждёт получателя
close(ch) // закрывает канал
}()
for num := range ch { // ← ПОЛУЧАТЕЛЬ читает из канала
fmt.Println(num)
}
Последовательность:
Время t1: Главная горутина создаёт background горутину
Время t2: Background горутина отправляет 1, 2, 3, 4 в буфер
(буфер полный)
Время t3: Background горутина пытается отправить 5
ch <- 5 // БЛОКИРУЕТСЯ (нет места)
Время t4: Главная горутина начинает читать из канала
for num := range ch
num = <-ch // читает 1
Время t5: Background горутина РАЗБЛОКИРУЕТСЯ
5 попадает в буфер (он освободился)
close(ch)
Время t6: Главная горутина продолжает читать
num = <-ch // читает 2, 3, 4, 5
range завершается (канал закрыт)
Визуализация буфера
После отправки 1, 2, 3, 4:
┌───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 4 │ buffer full (len=4, cap=4)
└───┴───┴───┴───┘
writeIdx = 0 (циклический буфер)
Сейчас ch <- 5 БЛОКИРУЕТСЯ:
Горотина ждёт, пока получатель прочитает хотя бы 1 элемент
Главная горутина читает из канала:
┌───┬───┬───┬───┐
│ _ │ 2 │ 3 │ 4 │ buffer (len=3, cap=4)
└───┴───┴───┴───┘
↑
readIdx
Теперь есть место! Background горутина отправляет 5:
┌───┬───┬───┬───┐
│ 5 │ 2 │ 3 │ 4 │ buffer (len=4, cap=4)
└───┴───┴───┴───┘
close(ch) // горутина закрывает канал
Главная горутина продолжает читать:
2, 3, 4, 5 // затем range завершается
Пошаговое выполнение
1. Создаёт буферизованный канал (buffer size = 4)
2. Запускает background горутину
3. Background горутина отправляет 1, 2, 3, 4 (все в буфер)
4. Background горутина пытается отправить 5 → БЛОКИРУЕТСЯ
5. Главная горутина начинает цикл range
6. for num := range ch читает из буфера
7. Первое чтение: num = 1 (буфер освобождается)
8. Background горутина РАЗБЛОКИРУЕТСЯ и отправляет 5
9. Background горутина закрывает канал
10. Главная горутина продолжает читать: 2, 3, 4, 5
11. Канал закрыт, range завершается
3. Что изменится, если убрать горутину?
ch := make(chan int, 4)
ch <- 1
ch <- 2
ch <- 3
ch <- 4
ch <- 5 // ❌ DEADLOCK! нет горутины для чтения
close(ch)
for num := range ch {
fmt.Println(num)
}
Результат: DEADLOCK (ошибка на выполнении)
fatal error: all goroutines are asleep - deadlock!
Почему происходит deadlock:
1. ch <- 1, 2, 3, 4 // успешно (буфер может вместить)
2. ch <- 5 // БЛОКИРУЕТСЯ (буфер полный, нет получателя)
3. for num := range ch // никогда не выполнится (главная горутина заблокирована на шаге 2)
4. ВСЕ горутины заблокированы → DEADLOCK
Примеры deadlock-ов с буферизованными каналами
Пример 1: Основной поток блокируется
ch := make(chan int, 2)
ch <- 1
ch <- 2
ch <- 3 // ❌ DEADLOCK (буфер полный, нет горутины для чтения)
Пример 2: Две горутины ждут друг друга
ch := make(chan int, 0) // безбуферный
go func() {
ch <- 1 // блокируется, ждёт получателя
}()
go func() {
x := <-ch // блокируется, ждёт отправителя
}()
time.Sleep(1 * time.Second) // обе горутины заблокированы
// ❌ DEADLOCK
Правильный код (исправленный вариант без горутины)
// Вариант 1: Прочитать ДО отправки всего
ch := make(chan int, 4)
ch <- 1
ch <- 2
ch <- 3
ch <- 4
// Читаем перед 5-й отправкой
fmt.Println(<-ch) // 1
ch <- 5 // теперь есть место
close(ch)
for num := range ch {
fmt.Println(num) // 2, 3, 4, 5
}
// Вариант 2: Использовать горутину (как в оригинале)
ch := make(chan int, 4)
go func() {
ch <- 1
ch <- 2
ch <- 3
ch <- 4
ch <- 5
close(ch)
}()
for num := range ch {
fmt.Println(num) // 1, 2, 3, 4, 5
}
Ключевые правила буферизованных каналов
- Отправка блокируется если буфер ПОЛНЫЙ и нет получателей
- Получение блокируется если буфер ПУСТ
- close() разрешено только отправителю
- range автоматически завершается когда канал закрыт и пуст
Полное резюме
| Действие | Безбуферный | Буферизованный (size=4) |
|---|---|---|
| send на полный канал | блокируется | блокируется |
| send на пустой | блокируется | успешно (первые 4) |
| recv на пустой | блокируется | блокируется |
| recv на полный | успешно | успешно |
| close() | разрешено | разрешено только отправителю |
| range после close | заканчивается | заканчивается |
Эта задача демонстрирует критическое понимание того, как буферизованные каналы предотвращают deadlock-и благодаря возможности кэшировать элементы.