Конкуренция в Go
Теория: Паттерны конкуренции в Go
Когда есть горутины, каналы и контекст, следующим шагом становятся устойчивые схемы их использования — паттерны. Они помогают не придумывать заново, как запускать десятки задач, собирать результаты, строить конвейеры и ограничивать параллелизм. Ниже — четыре базовых паттерна, на которых держится большинство конкурентных программ в Go.
Worker Pool
Пул воркеров нужен, когда есть множество однотипных задач и ограниченное число исполнителей. Вместо того чтобы запускать горутину на каждую задачу, создается фиксированное количество рабочих горутин, которые читают задачи из общего канала, обрабатывают их и складывают результаты в другой канал.
Главная идея: один канал — очередь задач, N воркеров — параллельные исполнители, еще один канал — результаты.
Ключевые моменты протокола:
jobsзаполняет и закрывает только постановщик задач; воркеры его не трогают.resultsзакрывается только послеwg.Wait(), то есть когда все воркеры гарантированно закончили запись.- ни одна горутина не зависает в ожидании данных и не пишет в закрытый канал.
Если нужен сохраненный порядок результатов, добавляется индекс задачи и буферизация на стороне сборщика (как в примере выше в комментариях к пулу).
Fan-out и Fan-in
Эти два паттерна описывают, как распределять и снова объединять потоки данных между горутинами. На практике они часто используются вместе.
Fan-out: разделить поток на несколько исполнителей
Одна очередь задач — несколько горутин, которые читают из нее и выполняют работу независимо. Это похоже на worker pool, но фокус именно на распределении потока.
Задачи обрабатываются ровно по одной на воркера, порядок не гарантируется — исполнение зависит от времени работы каждой горутины.
Fan-in: собрать несколько потоков в один
Обратная схема: несколько источников данных пишут в один канал, а одна горутина читает общий поток.
Гарантия здесь одна: доступ к каналу сериализован, можно безопасно писать из нескольких горутин без мьютексов.
В более сложных конструкциях fan-out и fan-in объединяются: входной поток распределяется по воркерам и затем собирается обратно в общий канал.
Pipeline
Пайплайн — это цепочка стадий обработки данных. Каждая стадия — отдельная горутина с двумя каналами: входным и выходным. Данные поступают на вход первой стадии, постепенно проходят все шаги обработки и выходят в виде итогового потока.
Главная идея: одна стадия — одна ответственность, один входной канал — один выходной.
Как только первая стадия отправляет первое значение, следующие начинают работу, не дожидаясь завершения всей генерации. Это и есть конвейер: разные этапы обработки идут параллельно.
В реальном коде почти всегда добавляется context.Context, чтобы уметь прерывать конвейер по сигналу — например, по таймауту запроса.
Шаблон повторяется на каждой стадии: чтение из входного канала, select с ctx.Done() и запись в выходной канал. Так конвейер всегда может аккуратно остановиться, не оставляя «висящих» горутин.
Правило для пайплайна то же, что и для других паттернов:
- канал закрывает только тот, кто в него пишет;
- каждая стадия закрывает свой
outпосле того, какinисчерпан или контекст отменен.
Ограничение числа параллельных задач (semaphore pattern)
Иногда горутин становится слишком много — они начинают перегружать внешнее API, файловую систему, БД или просто CPU. Полностью параллельность отключать не хочется, но нужно ограничить количество задач, которые могут выполняться одновременно. Для этого используют семафорный паттерн: горутины запускаются свободно, но доступ «внутрь критической секции» получает только ограниченное количество исполнителей.
Семафорный паттерн решает это одной конструкцией: канал с буфером фиксированного размера. Каждая горутина «входит» в семафор, записывая значение в канал, и «выходит», читая из него. Когда буфер заполнен, новые горутины блокируются, пока кто-то не освободит слот.
В любой момент времени одновременно работают не более limit горутин — остальные просто ждут свободного слота.
Если задачи могут отменяться, семафор часто комбинируют с контекстом:
Такое ограничение полезно при работе с внешними сервисами, лимитами на соединения, тяжелыми вычислениями и любыми местами, где «чем больше горутин, тем хуже».
Подведем итоги:
Worker Pool — фиксированное число воркеров обрабатывает очередь задач.
- Fan-out / Fan-in — разделяют входной поток на несколько исполнителей и объединяют результаты обратно.
- Pipeline — последовательность стадий обработки, каждая в своей горутине, связанная каналами.
- Семафорный паттерн — ограничивает число одновременно работающих горутин через буферизованный канал.
Все эти схемы строятся на одних и тех же примитивах — горутинах, каналах и контексте. Разница только в том, как именно соединяются каналы, кто их закрывает и откуда приходят сигналы отмены. Когда протоколы взаимодействия отточены, конкурентный код в Go становится предсказуемым и хорошо управляемым даже под высокой нагрузкой.


