Как горутина попадает на нитку вычислений процессора?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как горутина попадает на поток (нитку) ОС
Чтобы понять механизм планирования горутин, нужно разобраться в двухуровневой модели планирования Go: планировщик Go (Goroutine Scheduler) работает поверх планировщика потоков операционной системы.
1. Архитектура планировщика Go: модель M-P-G
Планировщик Go построен на трёх ключевых абстракциях:
- G (Goroutine) — сама горутина, её состояние и стек.
- M (Machine) — поток ОС (Machine Thread, "нить"), который непосредственно выполняется на ядре процессора.
- P (Processor) — виртуальный процессор (контекст планировщика), который связывает M и G. P содержит очередь готовых к выполнению горутин (runqueue).
// Упрощённое представление структур в runtime (псевдокод)
type g struct {
stack stack // стек горутины
sched gobuf // контекст исполнения (регистры)
status uint32 // статус: running, runnable, waiting и т.д.
}
type p struct {
runqhead uint32
runqtail uint32
runq [256]guintptr // локальная очередь из 256 горутин
m muintptr // привязанный поток M (или nil)
}
type m struct {
g0 *g // системная горутина
curg *g // текущая выполняемая горутина
p puintptr // привязанный P
thread uintptr // дескриптор потока ОС
}
2. Путь горутины на физический CPU
Процесс можно разбить на следующие этапы:
Этап 1: Создание и постановка в очередь
Когда вы запускаете горутину через go func() {...}, происходит:
- Аллокация структуры
Gи её стека. - Настройка точки входа (функции).
- Горутина помещается в локальную очередь (runqueue) текущего
P(виртуального процессора) исполняющей её горутины.
// Пример запуска горутины
go func() {
fmt.Println("Hello from goroutine")
}()
Этап 2: Выбор горутины для исполнения
Каждый P старается выполнять свои горутин из локальной очереди. Это work-stealing планировщик:
Pберёт следующуюGиз своей локальной очереди (runqueue).- Если очередь пуста,
Pпытается "украсть" (steal) половину горутин из очереди другогоP. - Если нечего украсть — проверяет глобальную очередь (используется реже).
Этап 3: Привязка к потоку ОС (M)
Pдолжен быть привязан к потоку ОС (M), чтобы исполнять код.- Если у
Pнет привязанногоM(например, все потоки заблокированы), планировщик может создать новый поток ОС через системный вызов (например,clone()в Linux). Mзапрашивается из пула или создаётся, затем привязывается кP.
Этап 4: Непосредственное исполнение на CPU
M— это поток ОС (pthread/thread в Windows). Только ОС решает, на каком физическом ядре CPU будет выполняться этот поток.- Когда планировщик ОС выделяет
Mквант времени на ядре:
- `M` выполняет машинный код, сгенерированный из инструкций горутины `G`.
- `M` через `P` продолжает выбирать и выполнять следующие горутины, если текущая завершилась или заблокировалась.
3. Ключевые моменты переключения контекста
- Кооперативная многозадачность между горутинами: Go-планировщик сам переключает горутины в критических точках (вызов канала, системный вызов,
go,runtime.Gosched()). Это не вытесняющее планирование на уровне горутин. - Вытесняющее планирование на уровне потоков ОС: Поток
Mможет быть вытеснен ОС в любой момент согласно политике планировщика ОС (round-robin и др.). - Блокирующие операции: Если горутина выполняет блокирующий системный вызов (например, файловый ввод-вывод), планировщик Go может отделить
PотM:
1. `P` освобождается и может быть использован другим `M`.
2. Создаётся новый поток ОС (`M`), если нужно, чтобы обслуживать другие горутины на этом `P`.
3. Когда системный вызов завершается, горутина возвращается в очередь.
4. Роль runtime и сет-полиллера (с версии Go 1.14)
Начиная с Go 1.14, реализована асинхронная вытесняющая многозадачность:
- Сет-полиллер (netpoller) — интегрирован в планировщик для асинхронного ввода-вывода.
- Сигналы вытеснения: Планировщик использует сигналы OS (например,
SIGURGв Unix) для прерывания длительно исполняющихся горутин, чтобы дать ход другим.
Итог: путь от создания до CPU
go→ создаётсяG→ помещается в очередьP.P(с привязаннымM) выбираетGиз своей очереди (или ворует у других).M(поток ОС) выполняет машинный кодGна физическом ядре CPU согласно планировщику ОС.- При блокировке или вытеснении
Mможет освободитьP, который затем обслуживает другие горутины через другие потокиM.
Эта модель обеспечивает высокую эффективность: миллионы горутин могут обслуживаться небольшим фиксированным числом потоков ОС, а быстрые переключения контекста происходят в пространстве пользователя без дорогостоящих системных вызовов.