Sobes.tech
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 або глобальної черги.

Принцип роботи:

  1. Нова горутина створюється і поміщається у локальну чергу доступного P.
  2. M, пов’язаний з P, бере горутину з локальної черги і виконує її.
  3. Якщо локальна черга порожня, M намагається вкрасти горутини у інших P.
  4. Якщо P блокується (наприклад, при системному виклику), зв’язаний з ним M від’єднується, і інший M зв’язується з цим P. Заблокований M може бути повторно використаний, коли системний виклик завершиться.
  5. Якщо горутина блокується на каналі або мьютексі, вона ставиться у чергу очікування, і планувальник вибирає іншу горутину для виконання на тому ж M.
  6. Планувальник виконує періодичні перевірки для витіснення довго працюючих горутин, дозволяючи іншим горутинам отримати час на виконання.

Точки витіснення (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).

Переваги такого підходу:

  • Масштабованість: Ефективне використання багатоядерних систем.
  • Низькі накладні витрати: Легкі горутини порівняно з потоками ОС.
  • Простота використання: Вбудоване планування у мові робить паралельне програмування більш доступним.