Ответ
Горутины исполняются на системных потоках ОС (OS threads), но управляются не напрямую операционной системой, а планировщиком Go (Go scheduler). Планировщик реализует модель M:P:G, обеспечивая эффективное мультиплексирование множества горутин (G) на небольшом количестве потоков ОС (M).
- G (Goroutine): Легковесный поток, управляемый Go. Имеет небольшой стек (от 2KB), который может расти по мере необходимости.
- M (Machine): Системный поток ОС. Непосредственно выполняет код.
- P (Processor): Контекст для выполнения. У каждого P есть локальная очередь горутин. Количество P по умолчанию равно количеству ядер ЦП (
GOMAXPROCS
).
Процесс работы:
- Планировщик привязывает M к P.
- M начинает выполнять горутины из очереди P.
- Если горутина блокируется (например, на системном вызове), M отсоединяется от P, а рантайм может создать новый M для этого P, чтобы продолжить выполнение других горутин.
- Если у одного P заканчиваются горутины, он может "украсть" их у другого P (механизм work-stealing) для балансировки нагрузки.
package main
import (
"fmt"
"time"
)
func main() {
go func() {
// Эта горутина будет поставлена в очередь
// и выполнена планировщиком на доступном потоке.
fmt.Println("Горутина выполняется")
}()
// Даем планировщику время на запуск горутины.
// Без этого main может завершиться раньше.
time.Sleep(time.Millisecond)
}
Таким образом, горутины не привязаны жестко к одному потоку ОС, что делает их очень легковесными и позволяет создавать сотни тысяч и даже миллионы горутин в одном приложении.