← Назад к вопросам

Что выведет код? Буферизованный канал

1.6 Junior🔥 281 комментариев
#Конкурентность и горутины#Основы Go

Условие

Определите, что выведет следующий код:

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)
    }
}

Вопросы

  1. Что выведет программа?
  2. Будет ли deadlock? Почему?
  3. Что изменится, если убрать горутину?

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Эта задача про буферизованные каналы и их поведение при отправке и получении данных. Ответ демонстрирует важное понимание 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
}

Ключевые правила буферизованных каналов

  1. Отправка блокируется если буфер ПОЛНЫЙ и нет получателей
  2. Получение блокируется если буфер ПУСТ
  3. close() разрешено только отправителю
  4. range автоматически завершается когда канал закрыт и пуст

Полное резюме

ДействиеБезбуферныйБуферизованный (size=4)
send на полный каналблокируетсяблокируется
send на пустойблокируетсяуспешно (первые 4)
recv на пустойблокируетсяблокируется
recv на полныйуспешноуспешно
close()разрешеноразрешено только отправителю
range после closeзаканчиваетсязаканчивается

Эта задача демонстрирует критическое понимание того, как буферизованные каналы предотвращают deadlock-и благодаря возможности кэшировать элементы.

Что выведет код? Буферизованный канал | PrepBro