Сколько есть очередей в модели GMP?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Очереди в модели GMP Go: иерархия и их назначение
В модели планирования GMP (Goroutine, Machine, Processor) языка Go используется не одна, а несколько типов очередей, организованных иерархически для эффективного распределения горутин. Их можно разделить на глобальные (общепроцессорные), локальные (per-P) и специальные. Подробный размотр:
1. Локальные очереди (Local Runqueues) — P.runq
Каждый логический процессор (P) имеет собственную локальную очередь фиксированного размера (обычно 256 элементов) в виде кольцевого буфера (runq [256]guintptr). В неё планировщик помещает новые горутины, созданные в контексте данного P.
// Упрощённая структура P (логического процессора)
type p struct {
// Локальная очередь (кольцевой буфер)
runq [256]guintptr
runqhead uint32
runqtail uint32
...
}
Назначение:
- Быстрое добавление/извлечение горутин без блокировок (только операции с атомарными счетчиками
headиtail). - Кэширование горутин "близко" к потоку ОС (M), который их исполняет, что повышает локальность данных.
- Приоритизация: планировщик сначала проверяет локальную очередь, затем глобальную.
2. Глобальная очередь (Global Runqueue) — sched.runq
Одна на весь процесс Go, представлена в структуре schedt (runtime.sched.runq). Это связанный список горутин, доступ к которому защищен глобальной мьютексой (sched.lock).
type schedt struct {
runq gQueue // Глобальная очередь (связанный список)
runqsize int32
lock mutex
...
}
Назначение:
- Балансировка нагрузки: когда локальная очередь P переполнена, половина её горутин (
runq/2) перемещается в глобальную очередь (runtime.runqputglobal). - Прием горутин от других P в рамках work-stealing (воровства задач) — если у P пустая локальная очередь, он забирает часть горутин из глобальной.
- Хранение горутин, которые не привязаны к конкретному P (например, разбуженные из сети/таймера).
3. Очередь воровства (Work-stealing Queue)
Не отдельная структура данных, а алгоритм доступа к очередям других P. Когда у P пустая локальная очередь и глобальная, он случайно выбирает другой P и "ворует" половину горутин из его локальной очереди (runtime.runqsteal).
Назначение:
- Динамическая балансировка нагрузки между процессорами.
- Предотвращение простаивания потоков ОС (M) при наличии готовых к исполнению горутин в других очередях.
4. Очередь отложенных вызовов (Deferred Call Queue)
Каждая горутина имеет свой стек, где хранятся отложенные вызовы defer. Это не очередь в общепринятом смысле, но логически — LIFO-очередь defer-структур в стеке горутины.
5. Очередь каналов (Channel Operation Queues)
Каждый канал (chan) имеет две очереди ожидания:
sendq— горутины, заблокированные на отправке в полный канал.recvq— горутины, заблокированные на получении из пустого канала.
Эти очереди — связанные списки (sudog), не относящиеся напрямую к планировщику, но влияющие на планирование: при успешной операции на канале горутина из очереди может быть перемещена в локальную очередь P.
Итог: сколько очередей?
Точное количество вариативно и зависит от:
- Количества процессоров
GOMAXPROCS— определяет число локальных очередей (по одной на P). - Количества каналов — каждый имеет по две очереди (
sendq,recvq). - Констант: одна глобальная очередь (
sched.runq).
Упрощённый расчёт для типичной программы:
Локальные очереди = GOMAXPROCS (например, 8)
+ Глобальная очередь = 1
+ Очереди каналов = 2 * количество_каналов
+ Деferred-очереди = количество_горутин (децентрализованно)
Таким образом, модель GMP использует распределённую систему очередей для минимизации конкуренции за ресурсы: большая часть работы выполняется через быстрые локальные очереди, а глобальная служит для балансировки и долгосрочного хранения. Эта архитектура — одна из ключевых причин высокой производительности планировщика Go при масштабировании на многоядерных системах.