Ответ
Планировщик 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 достигать высокой степени параллелизма с минимальными накладными расходами на переключение контекста по сравнению с традиционными потоками ОС.