Параллельные вычисления
CSP и каналы: Go
2012 год. Google запускает Go в production. Проблема: тысячи сервисов, миллиарды запросов, команды ненавидят сложность многопоточности в Java и C++. Решение - сделать конкурентность настолько дешёвой, чтобы она стала дефолтным способом написания кода.
- Docker и Kubernetes написаны на Go - оба интенсивно используют горутины и каналы для управления контейнерами
- Cloudflare обрабатывает 55 миллионов HTTP запросов в секунду на Go - горутины позволяют держать соединение открытым дёшево
- CockroachDB использует Go каналы для репликации и Raft consensus - тысячи горутин на ноду
Горутины: дешёвые потоки Go runtime
Горутина запускается с 2-8 KB стека (OS-поток - 1-8 MB). Go runtime мультиплексирует тысячи горутин на пул OS-потоков через планировщик M:N. Создание горутины занимает микросекунды и несколько KB - это меняет то, как можно писать конкурентный код.
Go runtime планировщик использует алгоритм work-stealing: незанятые OS-потоки воруют горутины из очередей занятых потоков. Это обеспечивает равномерную загрузку CPU без явного управления пулами потоков.
Сколько OS-потоков использует программа на Go с 10 000 горутин?
Каналы: типизированные трубы для данных
Мантра Go: 'Don't communicate by sharing memory; share memory by communicating.' Канал - типизированная очередь между горутинами. Небуферизованный канал синхронизирует отправителя и получателя. Буферизованный - позволяет отправить N элементов без блокировки.
Закрытие канала - способ сообщить получателям об окончании данных. После close(ch) чтение из канала возвращает нулевое значение и false. Запись в закрытый канал вызывает panic. Правило: закрывает канал только отправитель.
Что произойдёт при записи в закрытый канал в Go?
Select: мультиплексирование каналов
select позволяет горутине ждать на нескольких каналах одновременно. Выполняется первый готовый case. Если несколько готовы одновременно - выбор случаен. default делает select неблокирующим.
Два case в select одновременно готовы к выполнению. Что выберет Go runtime?
Fan-in / Fan-out: паттерны параллельной обработки
Fan-out: один входной канал, несколько горутин-обработчиков. Fan-in: несколько каналов сливаются в один. Комбинация - стандартный паттерн для параллельной обработки потоков данных.
Docker и Kubernetes интенсивно используют Go каналы: Docker streaming build output, Kubernetes event processing в контроллерах. Каждый этап pipeline - горутина, соединённая каналами. Backpressure автоматический - медленный consumer замедляет producer через блокировку канала.
В чём ключевое преимущество Fan-out через каналы перед явным thread pool?
Ключевые идеи
- Горутина стоит 2-8 KB и микросекунды - можно запускать миллионы без overhead OS-потоков.
- Каналы синхронизируют данные и управление: небуферизованный = рандеву, буферизованный = очередь.
- Fan-out/fan-in через каналы - идиоматичный Go паттерн для параллельной обработки с backpressure.
Связанные темы
CSP в Go - альтернатива другим моделям конкурентности:
- Actor Model (Erlang/Akka) — Схожая идея - не разделять память, но в акторах каждый имеет собственный mailbox, а Go каналы разделяются между горутинами
- Параллельные алгоритмы — Fan-out/fan-in паттерн Go - основа для реализации параллельных алгоритмов: sort, prefix sum, map-reduce
Вопросы для размышления
- В каком сценарии мьютекс эффективнее канала - когда горутин очень много и данные маленькие?
- Как паттерн context.WithCancel/WithTimeout через каналы реализует cooperative cancellation?
- Почему Go выбрал CSP вместо Actor Model, если обе модели решают ту же проблему?