Middle+
Разкажете за планирането на goroutine в Go.
sobes.tech AI
Отговор от AI
Планировчикът на горутините в Go използва модел M:N, където M горутини се мапват на N нишки на ОС. Той ефективно превключва между горутините, реализирайки невытесняваща многозадачност с точки на прекъсване.
Основните компоненти на модела 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.
- Планировчикът извършва периодични проверки за предсрочно прекъсване на дълго работещи горутини, позволявайки на други горутини да получат време за изпълнение.
Точки на предсрочно прекъсване (preemption points) възникват на определени места:
- Викащи функции/методи.
- Цикли, съдържащи извиквания на функции.
- Алокация на памет.
- Синхронизиращи примитиви (канали, мьютекси).
Пример за планиране:
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).
Предимства на този подход:
- Мащабируемост: Ефективно използване на многоядрени системи.
- Нисък накладен разход: Леките горутини в сравнение с потоците на ОС.
- Лесна употреба: Вграденото планиране в езика прави паралелното програмиране по-достъпно.