Назад к вопросам
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 или глобальной очереди.

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

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

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).

Преимущества такого подхода:

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