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