Ответ
Планировщик Go реализует модель M:N, где M горутин выполняются на N потоках операционной системы. Это позволяет эффективно управлять тысячами горутин на небольшом количестве системных потоков. В основе планировщика лежит концепция G-P-M.
G (Goroutine): Легковесный поток, управляемый рантаймом Go. Имеет свой собственный стек, который может расти и сжиматься. Это то, что вы создаете с помощью ключевого слова
go
.M (Machine): Системный поток (OS Thread), управляемый операционной системой. Это реальный исполнитель кода.
P (Processor): Контекст для выполнения. Это ключевая абстракция, которая связывает горутины с потоками. У каждого
P
есть локальная очередь исполняемых горутин. КоличествоP
по умолчанию равно количеству ядер CPU и контролируется переменнойruntime.GOMAXPROCS
.
Как это работает:
- Для выполнения
P
должен быть привязан кM
. P
беретG
из своей локальной очереди и выполняет ее наM
.- Work Stealing (Воровство работы): Если у одного
P
заканчиваются горутины в очереди, он может "украсть" половину горутин из очереди другогоP
, чтобы сбалансировать нагрузку и не простаивать. - Блокирующие системные вызовы: Если
G
наM
выполняет блокирующий системный вызов (например, чтение файла),P
отсоединяется от этогоM
(вместе с заблокированнойG
), находит другой свободныйM
(или создает новый) и продолжает выполнять другие горутины из своей очереди. Это предотвращает блокировку целого потока ОС.
func main() {
// Устанавливаем количество "процессоров" (P), которые будут
// одновременно выполнять горутины.
runtime.GOMAXPROCS(2)
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func(i int) {
defer wg.Done()
fmt.Printf("Горутина %d выполняетсяn", i)
// какая-то работа
time.Sleep(10 * time.Millisecond)
}(i)
}
wg.Wait()
}
Эта модель позволяет Go достигать высокой степени параллелизма с минимальными накладными расходами на переключение контекста по сравнению с традиционными потоками ОС.