← Назад к вопросам
Что такое паттерн GMP?
2.0 Middle🔥 171 комментариев
#Конкурентность и горутины#Основы Go
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Паттерн GMP (Goroutines, Channels, Multiplexing)
GMP — это архитектурный паттерн в Go для управления конкурентностью. Аббревиатура может означать разные интерпретации, но чаще всего здесь имеется в виду архитектура G:P:M (Goroutines : Processors : Machine threads) в runtime Go.
Архитектура G:P:M в Go Runtime
G — Goroutine
- Легковесный поток, управляемый runtime
- Может быть тысячи и миллионы
- Переключение контекста очень быстрое
M — Machine thread (OS Thread)
- Реальный поток операционной системы
- Стоит дорого (память, переключение контекста)
- Создаётся ограниченное количество
P — Processor (логический процессор)
- Виртуальный процессор в Go
- Количество обычно равно
runtime.GOMAXPROCS() - Привязан к одному M в момент времени
Визуализация архитектуры
┌─────────────────────────────────────┐
│ Heap (общая память для всех G) │
└─────────────────────────────────────┘
↓ ↓ ↓
┌────────┐ ┌────────┐ ┌────────┐
│ P1 │ │ P2 │ │ P3 │ (GOMAXPROCS)
└────────┘ └────────┘ └────────┘
↓ ↓ ↓
┌──────────────┬──────────────┬──────────────┐
│ M1 (OS) │ M2 (OS) │ M3 (OS) │
└──────────────┴──────────────┴──────────────┘
↓ ↓ ↓
┌────────┐ ┌────────┐ ┌────────┐
│ G1 G2 │ │ G3 G4 │ │ G5 G6 │
│ G7 ... │ │ ... │ │ ... │
└────────┘ └────────┘ └────────┘
Практический пример: M:N scheduling
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
// По умолчанию GOMAXPROCS = количество ядер
fmt.Printf("CPU cores: %d\n", runtime.NumCPU())
fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(-1))
// Можно явно ограничить
runtime.GOMAXPROCS(2)
// Создаём много горутин
for i := 0; i < 10; i++ {
go func(n int) {
for j := 0; j < 5; j++ {
fmt.Printf("G%d iteration %d\n", n, j)
time.Sleep(10 * time.Millisecond)
}
}(i)
}
time.Sleep(2 * time.Second)
}
Работа планировщика
Шаг 1: Инициализация
- Runtime создаёт P'ы (процессоры) согласно GOMAXPROCS
- Для каждого P создаётся локальная очередь (Local Run Queue, LRQ)
- Глобальная очередь (Global Run Queue, GRQ) для избытка горутин
Шаг 2: Распределение работы
go func() { // Создаёт новую G
// эта G попадает в LRQ ближайшего P
// или в GRQ если все LRQ полны
}()
Шаг 3: Планирование
- Каждый M:P выполняет G из своей LRQ
- Если LRQ пуста, берёт из GRQ
- Если и там нет, занимается работой stealing (берёт из LRQ другого P)
Системные вызовы и блокировка
func main() {
// Блокирующий системный вызов
go func() {
file, _ := os.Open("file.txt") // Блокирует M
// Если P был привязан к этому M,
// runtime отвязывает P и привязывает к свободному M
fmt.Println(file.Name())
}()
time.Sleep(time.Second)
}
Это ключевое отличие от других систем: когда goroutine выполняет системный вызов, P переключается на другой M, чтобы выполнять другие горутины.
Типичный сценарий с M:N
Стартовое состояние:
G: 100 горутин
P: 4 процессора (GOMAXPROCS=4)
M: 1-2 OS потока
Сценарий:
1. Горутины распределены в LRQ каждого P
2. Каждый M:P выполняет G из своей очереди
3. Когда G заканчивается, M:P берёт следующую G
4. Если G выполняет блокирующий вызов:
- M блокируется в системном вызове
- Runtime создаёт новый M для P
- Новый M:P продолжает выполнять другие G
5. Когда блокирующий вызов завершается:
- M пробуждается, но может быть распределён на свободную P
Как настроить GOMAXPROCS
import (
"runtime"
"runtime/debug"
)
func main() {
// Получить текущее значение
current := runtime.GOMAXPROCS(-1)
fmt.Println("Current GOMAXPROCS:", current)
// Установить новое значение
old := runtime.GOMAXPROCS(2)
fmt.Printf("Changed from %d to 2\n", old)
// Совет: для CPU-bound задач GOMAXPROCS должен быть ≈ CPU cores
optimalProcs := runtime.NumCPU()
runtime.GOMAXPROCS(optimalProcs)
}
Практические выводы
- GMP архитектура позволяет эффективно управлять конкурентностью
- Горутины очень дешёвые — можно создавать десятки тысяч
- OS потоки дорогие — runtime создаёт ровно столько, сколько нужно
- Work stealing гарантирует справедливое распределение нагрузки
- GOMAXPROCS должен быть равен количеству ядер для CPU-bound работы
- Блокирующие вызовы не замораживают другие горутины — runtime переключает P
Эта архитектура — одна из главных причин популярности Go для высоконагруженных систем.