← Назад к вопросам

Почему горутина переключается быстрее, чем тред?

2.0 Middle🔥 61 комментариев
#Конкурентность и горутины

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему горутина переключается быстрее, чем тред?

Горутины в Go переключаются быстрее, чем традиционные потоки операционной системы, из-за кооперативной многозадачности, легковесной реализации и интеграции с планировщиком Go.

1. Кооперативная многозадачность против вытесняющей

Горутины используют кооперативную модель: они сами уступают управление в ключевых точках (вызовы runtime.Gosched(), блокировки на I/O, каналы, системные вызовы). Потоки ОС используют вытесняющую многозадачность: планировщик ОС силой переключает потоки по таймеру, независимо от их состояния.

Контекст переключения горутин включает только регистры и указатель стека, а контекст потока ОС требует сохранения полного состояния (регистры, стек, сигналы, состояние файловых дескрипторов). Пример переключения горутины:

package main

func main() {
    go func() {
        // Горутина уступит управление при вызове Gosched
        runtime.Gosched()
    }()
}

Вытеснение потоков требует вмешательства ОС и полного сохранения контекста, что тяжелее.

2. Легковесность горутин

Стек горутин динамический и маленький (начально 2KB, растёт/сжимается). Стек потока ОС фиксированный и большой (обычно 1-8MB).

Планировщик Go управляет горутинами в пользовательском пространстве, избегая перехода в ядро ОС. Потоки управляются ядром ОС, что требует системных вызовов.

Пример создания тысяч горутин:

package main

import "time"

func worker(id int) {
    time.Sleep(time.Second)
}

func main() {
    for i := 0; i < 10000; i++ {
        go worker(i) // Легковесные горутины
    }
}

Создание 10000 потоков ОС перегружает систему.

3. Планировщик Go: модель M:N

Планировщик Go мультиплексирует M горутин на N потоков ОС (обычно N = число ядер). Это даёт:

  • Меньше переключений потоков ОС: горутины переключаются внутри одного потока ОС.
  • Оптимизированное распределение: планировщик Go балансирует горутины между потоками ОС.
// Планировщик эффективно распределяет горутины на 4 потока ОС (4 ядра)
runtime.GOMAXPROCS(4)

Переключение потоков ОС требует системного вызова и полного сохранения контекста.

4. Эффективная работа с блокирующими операциями

При блокирующих операциях (I/O, системные вызовы) планировщик Go отсоединяет горутину от потока ОС, позволяя другим горутинам использовать поток.

Пример работы с сетью:

package main

import "net/http"

func fetch(url string) {
    resp, _ := http.Get(url) // Блокирующий вызов
    // Планировщик переключит горутину, пока ждём ответ
}

Поток ОС при блокировании просто ждёт, переключение требует вытеснения.

5. Статистика и профилирование

Переключение горутин занимает десятки-сотни наносекунд, переключение потоков ОС — микросекунды (включая переход в ядро). Планировщик Go оптимизирует:

  • Локальные очереди для каждого потока ОС.
  • Глобальную очередь для балансировки.
  • Работа в пространстве пользователя.

Пример профилирования:

import "runtime"

func main() {
    // Профилирование планировщика
    runtime.SetBlockProfileRate(1)
}

Сравнение в таблице

КритерийГорутиныПотоки ОС
Модель многозадачностиКооперативнаяВытесняющая
Размер стека2KB (динамический)1-8MB (фиксированный)
Контекст переключенияРегистры + указатель стекаПолный контекст ОС
ПланировщикПользовательский (Go)Ядро ОС
Создание/управлениеДесятки микросекундМиллисекунды
ПереключениеНаносекундыМикросекунды

Заключение

Горутины переключаются быстрее, благодаря:

  • Кооперативной модели без накладных вытеснений.
  • Легковесным стекам с динамическим размером.
  • Пользовательскому планировщику без перехода в ядро.
  • Модели M:N с минимальным использованием потоков ОС.

Это делает Go эффективным для высоконагруженных параллельных систем с тысячами одновременных задач, где потоки ОС непрактичны.

Почему горутина переключается быстрее, чем тред? | PrepBro