1. Модель конкурентности Go и запуск горутин
Модель конкурентности Go и запуск горутин
Зачем Go нужна конкурентность
Современные программы почти всегда делают несколько дел одновременно: обслуживают сеть, читают файлы, обрабатывают запросы, ждут таймеры. Go спроектирован так, чтобы конкурентность была обычной частью кода, а не сложным «низкоуровневым» трюком.
Важно различать два понятия:
Go делает конкурентность удобной через горутины, а параллелизм достигается за счёт планировщика и доступных ресурсов.
> “Concurrency is not parallelism.” — Rob Pike (Concurrency is not parallelism)
Что такое горутина
Горутина — это лёгкая единица выполнения в Go, управляемая рантаймом Go, а не напрямую операционной системой.
Ключевые свойства горутин:
Горутина запускается с помощью оператора go.
Оператор go и запуск функции
Синтаксис:
Это означает:
f() будет выполнена в отдельной горутине.f() и продолжает выполняться.Важно: вы можете запускать не только именованные функции, но и анонимные.
В этом примере строка из горутины может не успеть напечататься. Почему — разберём дальше.
Официальная спецификация: The Go Programming Language Specification — Go statements
Что происходит, когда завершается main
В Go программа завершается, когда завершается функция main в пакете main. При этом:
Это одна из самых частых ошибок новичков: «я запустил горутину, но она ничего не сделала». На самом деле она просто не успела.
Модель выполнения Go: планировщик и сущности G, M, P
Чтобы понять поведение горутин, полезно знать упрощённую модель планировщика Go:
Планировщик сопоставляет множество G с меньшим количеством M, используя P как «квоты на исполнение».
!Упрощённая схема, как горутины распределяются по потокам через сущности G-M-P
Сколько Go-кода может выполняться параллельно
За параллелизм отвечает лимит, связанный с GOMAXPROCS:
GOMAXPROCS задаёт, сколько P доступно рантаймуПо умолчанию Go ставит GOMAXPROCS равным числу доступных CPU.
Документация: runtime.GOMAXPROCS
Почему горутины «лёгкие»
Горутины дешевле потоков ОС по нескольким причинам:
Практический вывод: горутины удобны как «единицы работы» для конкурентного дизайна.
Как корректно дождаться завершения горутины
Так как main не ждёт другие горутины, ожидание нужно организовывать явно.
Ожидание через sync.WaitGroup
sync.WaitGroup — базовый инструмент, чтобы дождаться завершения группы горутин.
Правила, которые стоит запомнить:
wg.Add(n) вызывайте до запуска горутин, чтобы не поймать гонку по счётчику.wg.Done() удобно ставить через defer, чтобы не забыть при раннем выходе.Документация: sync.WaitGroup
Типичные ошибки при запуске горутин
Захват переменной цикла в замыкании
Одна из самых известных ловушек — запуск горутин внутри цикла и использование переменной цикла внутри анонимной функции.
Плохой вариант:
Исправления:
time.Sleep как “ожидание”
Иногда встречается подход: “после запуска горутины посплю, чтобы она успела”. Это ненадёжно:
Для ожидания используйте WaitGroup, каналы или другие синхронизационные примитивы (каналы подробно пойдут в следующих статьях).
Гонки данных
Если несколько горутин читают и пишут в общие переменные без синхронизации, возникает гонка данных. Итоги:
Пока достаточно запомнить правило:
sync)Когда стоит запускать горутину
Запуск горутины — это не “ускоритель по умолчанию”. Имеет смысл, когда:
Не стоит бездумно запускать горутину на каждый чих, если:
Что дальше в курсе
В этой статье мы разобрали, как Go запускает конкурентные задачи через горутины и почему важно управлять их жизненным циклом. Дальше логичный шаг — научиться безопасно обмениваться данными между горутинами и строить понятные конкурентные схемы.
В следующей статье перейдём к каналам: как они работают, как блокируют и синхронизируют выполнение, и как строить на них простой и надёжный обмен данными.