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

Какого типа многозадачности планировщик в Go

1.3 Junior🔥 181 комментариев
#Конкурентность и горутины#Основы Go

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

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

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

Тип многозадачности в Go: конкурентная модель с кооперативной планировкой

В языке Go реализована конкурентная многозадачность (concurrency) на основе горутин (goroutines) и кооперативного планировщика (cooperative scheduler), который является частью среды выполнения Go (runtime). Это принципиально отличается от традиционной многопоточности с вытеснением (preemptive multitasking), используемой в большинстве языков.

Горутины как легковесные потоки

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

// Пример создания горутины
package main

import (
    "fmt"
    "time"
)

func main() {
    // Запуск горутины с помощью ключевого слова go
    go printNumbers()
    
    // Главная горутина продолжает выполнение
    fmt.Println("Главная горутина работает")
    
    // Даем время для выполнения горутины
    time.Sleep(1 * time.Second)
}

func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Printf("Число: %d\n", i)
        time.Sleep(100 * time.Millisecond)
    }
}

Архитектура планировщика Go

Планировщик Go (GMP модель) состоит из трех основных компонентов:

  • G (Goroutine) — горутина, единица выполнения
  • M (Machine) — поток ОС (machine thread)
  • P (Processor) — логический процессор, контекст выполнения

Кооперативный характер планировщика

Планировщик Go является кооперативным (cooperative), что означает:

  1. Горутины добровольно уступают управление в определенных точках:

    • При вызове блокирующих операций (системные вызовы, каналы, sleep)
    • При вызове runtime.Gosched()
    • При операциях сбора мусора
    • В конце функции
  2. Отсутствие принудительного вытеснения по времени (в современных версиях Go есть ограниченная форма вытеснения на основе меток)

package main

import (
    "fmt"
    "runtime"
    "time"
)

func greedyGoroutine(id int) {
    for i := 0; i < 5; i++ {
        // Без явного указания уступить управление, горутина может монополизировать P
        fmt.Printf("Горутина %d: шаг %d\n", id, i)
        
        // Явная уступка управления планировщику
        runtime.Gosched()
    }
}

func main() {
    // Запускаем несколько "жадных" горутин
    for i := 1; i <= 3; i++ {
        go greedyGoroutine(i)
    }
    
    time.Sleep(1 * time.Second)
}

Эволюция планировщика: от чисто кооперативного к гибридному

В современных версиях Go (начиная с 1.14) реализована ограниченная форма вытеснения:

  • Точки приостановки (preemption points) вставляются компилятором
  • Вытеснение на основе меток — планировщик может прервать долго работающую горутину
  • Системные вызовы — при блокирующих вызовах горутина освобождает P
// Современный планировщик может прервать долгую операцию
func longRunningTask() {
    // До Go 1.14 такая функция могла блокировать планировщик
    // Теперь планировщик может прервать выполнение
    start := time.Now()
    for time.Since(start) < 500*time.Millisecond {
        // Интенсивные вычисления
    }
    fmt.Println("Долгая задача завершена")
}

Преимущества кооперативной модели в Go

  1. Низкие накладные расходы на переключение контекста
  2. Эффективное использование кэша процессора благодаря меньшему количеству переключений
  3. Детерминированное поведение в определенных сценариях
  4. Простота синхронизации — отсутствие инвалидации кэша из-за частых переключений

Практические следствия для разработчика

package main

import (
    "fmt"
    "runtime"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        // Точка, где горутина может уступить управление
        result := processJob(job)
        results <- result
        
        // Или явное указание уступить управление
        if job%10 == 0 {
            runtime.Gosched()
        }
    }
}

func processJob(job int) int {
    // Имитация обработки
    time.Sleep(10 * time.Millisecond)
    return job * 2
}

func main() {
    // Настройка количества логических процессоров
    runtime.GOMAXPROCS(4)
    
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    
    // Запуск пула воркеров
    for w := 1; w <= 4; w++ {
        go worker(w, jobs, results)
    }
    
    // Отправка заданий
    for j := 1; j <= 20; j++ {
        jobs <- j
    }
    close(jobs)
    
    // Сбор результатов
    for r := 1; r <= 20; r++ {
        fmt.Println(<-results)
    }
}

Заключение

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

Какого типа многозадачности планировщик в Go | PrepBro