> Как реализовать паттерн fan-out fan-in в Go с использованием горутин? (Go)
Уровень: senior · Роль: backend · Категория: Технические вопросы
Компании: Axel Pro
Стек: Go
> Пример ответа
Короткий ответ
Fan-out fan-in - это паттерн параллельной обработки: несколько горутин (fan-out) читают из одного входного канала, а результаты объединяются в один выходной канал (fan-in). В Go реализуется через sync.WaitGroup для ожидания завершения воркеров и отдельную горутину-агрегатор, которая закрывает выходной канал после завершения всех воркеров. Ключевой момент - корректное закрытие каналов и обработка ошибок без deadlock.
Подробное объяснение
Паттерн состоит из трёх этапов:
-
Fan-out - запуск N горутин-воркеров, каждая из которых читает из общего входного канала
jobs. Go гарантирует, что каждый элемент будет доставлен ровно одному воркеру. -
Обработка - каждый воркер выполняет независимую операцию над полученным элементом и отправляет результат в общий выходной канал
results. -
Fan-in - агрегатор (обычно отдельная горутина) собирает результаты из выходного канала до его закрытия. Закрытие происходит после завершения всех воркеров.
Критический момент - закрытие выходного канала. Если закрыть его раньше, чем все воркеры закончат отправку, получим panic при отправке в закрытый канал. Если не закрыть вовсе - потребитель заблокируется навсегда. Поэтому используется sync.WaitGroup: каждый воркер вызывает wg.Done() после завершения, а отдельная горутина ждёт wg.Wait() и затем закрывает results.
Варианты реализации:
- Простой - все воркеры пишут в один канал, агрегатор читает из него.
- С буферизацией - буферизованные каналы снижают блокировки, но требуют аккуратного выбора размера буфера.
- С контекстом -
context.Contextдля отмены при ошибках или таймаутах. - С errgroup -
golang.org/x/sync/errgroupупрощает обработку ошибок и отмену.
На практике
Для senior-позиции важно показать понимание trade-off:
- Количество воркеров - не равно числу элементов. Оптимально
runtime.NumCPU()или настраиваемое значение. - Ошибки - если один воркер упал, остальные должны получить сигнал остановки через
context. - Порядок результатов - fan-out fan-in не гарантирует порядок. Если порядок важен, нужна индексация результатов и сортировка после сбора.
- Буферизация - при большом потоке данных буфер канала
resultsдолжен быть достаточным, иначе воркеры будут блокироваться на отправке.
Практический пример из реального бэкенда: обработка пачки HTTP-запросов к внешнему API, где каждый запрос независим, а результаты агрегируются в общий ответ.
Пример кода
GOpackage mainimport ("context""fmt""sync")func fanOutFanIn(ctx context.Context, jobs <-chan int, workers int) <-chan int {results := make(chan int)var wg sync.WaitGroup// fan-out: запускаем воркеровfor i := 0; i < workers; i++ {wg.Add(1)go func() {defer wg.Done()for job := range jobs {select {case <-ctx.Done():returncase results <- process(job):}}}()}// fan-in: закрываем results после завершения всех воркеровgo func() {wg.Wait()close(results)}()return results}func process(n int) int {// имитация работыreturn n * 2}func main() {ctx := context.Background()jobs := make(chan int, 10)for i := 1; i <= 10; i++ {jobs <- i}close(jobs)results := fanOutFanIn(ctx, jobs, 3)for res := range results {fmt.Println(res)}}
Как отвечать на собеседовании
- Начни с определения паттерна и его назначения - параллельная обработка независимых задач.
- Опиши структуру: входной канал, воркеры, выходной канал, агрегатор.
- Обязательно упомяни
sync.WaitGroupи почему без него будет deadlock или panic. - Покажи понимание edge cases: закрытие каналов, обработка ошибок, отмена через context.
- Если спросят про производительность - расскажи про буферизацию, количество воркеров,
runtime.GOMAXPROCS. - Предложи альтернативы:
errgroup,worker poolбиблиотеки,channelс direction (send-only/receive-only).
Что проверяет интервьюер
- Понимание модели конкурентности Go: горутины, каналы,
select. - Умение работать с
sync.WaitGroupи корректно закрывать каналы. - Навыки проектирования конкурентных систем: баланс между параллелизмом и блокировками.
- Обработка ошибок и отмены операций.
- Способность объяснить trade-off между простотой и производительностью.
Типичные ошибки
- Закрытие канала в воркере - приводит к panic при отправке из других воркеров.
- Отсутствие
wg.Wait()передclose(results)- deadlock у потребителя. - Игнорирование context - при ошибке одного воркера остальные продолжают работать впустую.
- Слишком много воркеров - деградация производительности из-за переключения контекста.
- Забыли закрыть входной канал - воркеры будут вечно ждать новые задачи.
- Необработанные ошибки внутри воркера - результат теряется, а ошибка молча игнорируется.
- Использование общего
sync.WaitGroupбезAddперед запуском - гонка данных.
> Похожие задачи по backend
Что происходит с внешним сервисом при повторных обращениях во время сбоя
Что такое селективность индекса и почему не всегда индекс используется?
Что происходит в программе при отсутствии доступной памяти
В чем различия между стеком и хипом в Go
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью