Назад к вопросам
Middle+
119
questionbank
Расскажите о планировании goroutine в Go.
Sobes Copilot
Получайте ответы в реальном времени
Ответ от нейросети
sobes.tech AI
Планировщик горутин в Go использует модель M:N, где M горутин отображаются на N потоков ОС. Он эффективно переключает горутины между потоками, реализуя невытесняющую многозадачность с точками останова (преemption points).
Основные компоненты GPM модели:
- G (Goroutine): Абстракция исполняемой единицы кода, легковесный поток, управляемый планировщиком Go.
- P (Processor): Логический процессор, представляющий собой контекст локальной очереди горутин и ресурсов, таких как мьютексы и планировщик. Количество P по умолчанию определяется количеством логических ядер процессора, но может быть изменено через
GOMAXPROCS. - M (Machine/Thread): Поток операционной системы, который исполняет код. M связан с P и исполняет горутины из локальной очереди P или глобальной очереди.
Принцип работы:
- Новая горутина создается и помещается в локальную очередь доступного P.
- M, связанный с P, берет горутину из локальной очереди и исполняет ее.
- Если локальная очередь пуста, M пытается украсть горутины у других P.
- Если P блокируется (например, при выполнении системного вызова), связанный с ним M отсоединяется, и другой M связывается с этим P. Заблокированный M может быть повторно использован, когда системный вызов завершился.
- Если горутина блокируется на канале или мьютексе, она ставится в очередь ожидания, и планировщик выбирает другую горутину для исполнения на том же M.
- Планировщик выполняет периодические проверки для вытеснения долго работающих горутин, позволяя другим горутинам получить время на исполнение.
Points of preemption (точки вытеснения) возникают в определенных местах:
- Вызовы функций/методов.
- Циклы, содержащие вызовы функций.
- Выделение памяти.
- Синхронизационные примитивы (каналы, мьютексы).
Пример планирования:
package main
import (
"fmt"
"runtime"
"time"
)
func worker(id int) {
fmt.Printf("Горутина %d начала работу\n", id)
time.Sleep(time.Second) // Имитация работы
fmt.Printf("Горутина %d закончила работу\n", id)
}
func main() {
runtime.GOMAXPROCS(2) // Устанавливаем количество логических процессоров
for i := 1; i <= 5; i++ {
go worker(i) // Создаем горутины
}
time.Sleep(3 * time.Second) // Ждем завершения горутин
fmt.Println("Основная горутина завершена")
}
В этом примере 5 горутин планируются на 2 логических процессорах. Планировщик Go будет распределять эти горутины между доступными потоками ОС, используя локальные и, возможно, глобальные очереди, а также механизм "кражи" работы (work stealing).
Преимущества такого подхода:
- Масштабируемость: Эффективное использование многоядерных систем.
- Низкие накладные расходы: Легковесные горутины по сравнению с потоками ОС.
- Простота использования: Интегрированное в язык планирование делает параллельное программирование более доступным.