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

Объединение каналов (Fan-In)

1.7 Middle🔥 261 комментариев
#Конкурентность и горутины#Основы Go

Условие

Напишите функцию, которая принимает несколько каналов для чтения целых чисел и объединяет их в один канал для чтения.

Сигнатура

func merge(channels ...<-chan int) <-chan int

Требования

  • Функция должна возвращать канал, из которого можно читать значения из всех входных каналов
  • Функция должна корректно закрывать выходной канал, когда все входные каналы закрыты
  • Использовать горутины и sync.WaitGroup для ожидания завершения

Пример использования

ch1 := make(chan int)
ch2 := make(chan int)
merged := merge(ch1, ch2)

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

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

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

Решение

Это классический паттерн Fan-In в Go — объединение нескольких каналов в один. Задача требует корректного управления жизненным циклом каналов и синхронизации горутин.

Подход

  1. Создаём выходной канал
  2. Для каждого входного канала запускаем горутину, которая читает из него
  3. Используем sync.WaitGroup для отслеживания завершения всех горутин
  4. Закрываем выходной канал, когда все входные каналы исчерпаны

Реализация

package main

import "sync"

func merge(channels ...<-chan int) <-chan int {
    out := make(chan int)
    var wg sync.WaitGroup
    
    // Функция для чтения из одного канала и отправки в выходной
    sendToOut := func(ch <-chan int) {
        defer wg.Done()
        for val := range ch {
            out <- val
        }
    }
    
    // Запускаем горутину для каждого входного канала
    wg.Add(len(channels))
    for _, ch := range channels {
        go sendToOut(ch)
    }
    
    // Отдельная горутина для закрытия выходного канала
    go func() {
        wg.Wait()          // Ждём, пока все горутины завершатся
        close(out)         // Закрываем выходной канал
    }()
    
    return out
}

Почему именно этот подход

  • WaitGroup: правильно отслеживает, когда все горутины завершены
  • defer wg.Done(): гарантирует вызов даже при паник
  • Отдельная горутина для close: избегаем deadlock и ошибки "send on closed channel"
  • range ch: автоматически обнаруживает закрытие канала

Пример использования

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    merged := merge(ch1, ch2)
    
    // Запускаем горутины для отправки данных
    go func() {
        ch1 <- 1
        ch1 <- 2
        close(ch1)
    }()
    
    go func() {
        ch2 <- 3
        ch2 <- 4
        close(ch2)
    }()
    
    // Читаем из объединённого канала
    for val := range merged {
        println(val)  // выведет: 1, 2, 3, 4 (или другой порядок)
    }
}

Возможные ошибки и как их избежать

❌ Ошибка: закрывать выходной канал в основной горутине

  • Может привести к deadlock, если горутины ещё пишут

✅ Правильно: использовать отдельную горутину + WaitGroup

  • Гарантирует, что все писатели завершены до close

❌ Ошибка: забыть закрыть выходной канал

  • Читатель будет ждать вечно

Временная сложность

  • O(n) — где n общее количество значений во всех каналах
  • Каждое значение обрабатывается ровно один раз

Этот паттерн — стандартный способ работы с множеством каналов в Go и часто используется в production коде для обработки параллельных потоков данных.