Как устроены очереди горутин в планировщике Go?

Ответ

Планировщик Go использует две основные очереди для управления горутинами: локальную и глобальную.

1. Локальная очередь (LRQ - Local Run Queue)

  • Привязка: Каждому P (процессору/контексту выполнения) принадлежит своя локальная очередь.
  • Назначение: Это основная очередь, из которой P берет горутины для выполнения. Такой подход минимизирует блокировки и contention, так как каждый P работает со своей очередью.
  • Ограничение: Размер LRQ ограничен (обычно 256 горутин). Если при создании новой горутины LRQ переполнена, половина горутин из нее перемещается в глобальную очередь.

2. Глобальная очередь (GRQ - Global Run Queue)

  • Назначение: Это общая, единая для всех P очередь. Она служит в качестве "резерва".
  • Когда горутины попадают в GRQ?
    • При переполнении локальной очереди (LRQ).
    • Горутины, которые были заблокированы на системном вызове и теперь снова готовы к выполнению.
    • Иногда — новые созданные горутины.

Механизм "Work-Stealing" (Воровство работы)

Это ключевой механизм балансировки нагрузки:

  1. P в первую очередь ищет горутины в своей локальной очереди (LRQ).
  2. Если LRQ пуста, P пытается найти работу в следующем порядке:
    • Проверяет глобальную очередь (GRQ).
    • Если GRQ пуста, P становится "вором": он пытается "украсть" половину горутин из LRQ другого, случайного P.
    • Если и это не удалось, P проверяет сетевой поллер на наличие готовых к выполнению горутин (ожидающих I/O).

Этот механизм позволяет эффективно распределять работу и избегать простоя системных потоков (M).