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

Как работает Asynchronous Preemptible?

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

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

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

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

Механизм Asynchronous Preemptible в Go

Asynchronous Preemptible (асинхронная вытесняющая многозадачность) — это ключевой механизм планировщика Go, который позволяет прерывать выполнение goroutine на любом этапе, даже в длительных вычислительных циклах без вызовов блокирующих операций. Этот механизм был введён в Go 1.14 для решения проблемы "голодания" других горутин из-за монопольного использования потока ОС.

Проблема, которую решает механизм

До версии 1.14 планировщик Go использовал cooperative preemption (кооперативную многозадачность), где горутина добровольно уступала поток только при вызове определённых операций (каналы, системные вызовы, runtime.Gosched()). Это приводило к проблемам:

// Пример проблемы до Go 1.14
func greedyGoroutine() {
    for i := 0; ; i++ {
        // Долгий вычисляемый цикл без точек yield
        // Может заблокировать другие горутины на той же нити
    }
}

Как работает Asynchronous Preemption

Механизм основан на сигналах ОС и модификации кода выполняющейся горутины:

  1. Инициация прерывания: планировщик периодически отправляет сигнал SIGURG целевой горутине.

  2. Обработка сигнала: в обработчике сигнала проверяется, должна ли текущая горутина уступить поток.

  3. Модификация контекста: если требуется прерывание, сохраняется контекст выполнения и управление передаётся планировщику.

// Упрощённая схема работы (псевдокод)
func asyncPreempt() {
    // 1. Получен сигнал SIGURG
    if shouldPreempt(currentGoroutine) {
        // 2. Сохраняем регистры и состояние
        saveContext()
        
        // 3. Переключаемся на планировщик
        switchToScheduler()
    }
}

Техническая реализация

На низком уровне механизм использует:

  • Сигналы POSIX: В Unix-системах используется SIGURG, специально выбранный как редко используемый в пользовательских программах.
  • Windows APC: В Windows используется Asynchronous Procedure Calls.
  • Инъекция кода: Планировщик модифицирует стек и регистры для безопасного прерывания.
; Примерная схема сохранения контекста при прерывании
SAVE_CONTEXT:
    MOVQ    AX, saved_ax
    MOVQ    BX, saved_bx
    MOVQ    IP, saved_ip
    ; ... сохранение всех регистров
    JMP     scheduler_entry

Практическое значение

Asynchronous Preemption обеспечивает:

  1. Честное распределение CPU: Даже "жадные" горутины не могут монополизировать поток.
  2. Улучшенную latency: Горутины, ожидающие I/O, получают шанс выполниться быстрее.
  3. Предсказуемость: Система ведёт себя более предсказуемо под нагрузкой.

Ограничения и нюансы

  • Настройка частоты: Частота прерываний контролируется переменной forcePreemptNS (по умолчанию 10ms).
  • CGO и системные вызовы: Прерывание невозможно во время выполнения C-кода или блокирующих системных вызовов.
  • Производительность: Механизм добавляет небольшие накладные расходы (обычно <1%).

Пример наблюдения работы механизма

package main

import (
    "runtime"
    "time"
)

func main() {
    // Горутина с долгим вычислением
    go func() {
        for i := 0; i < 1e9; i++ {
            // Без асинхронного прерывания заблокировала бы другие горутины
        }
    }()
    
    // Эта горутина должна получить время CPU благодаря preemption
    go func() {
        for i := 0; i < 5; i++ {
            println("I'm getting CPU time!")
            time.Sleep(100 * time.Millisecond)
        }
    }()
    
    time.Sleep(2 * time.Second)
}

Важность для конкурентности

Без Asynchronous Preemptible разработчикам приходилось бы вручную добавлять точки yield в длительные вычисления. Теперь планировщик гарантирует, что даже плохо написанный код не нарушит работу всей программы.

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

Как работает Asynchronous Preemptible? | PrepBro