Что такое горутины и как работает их планировщик в Go?

Ответ

Горутины (goroutines) — это легковесные потоки исполнения, которые управляются средой выполнения (runtime) Go, а не напрямую операционной системой.

Ключевая концепция — планировщик Go (Go Scheduler)

Планировщик реализует модель M:P:G:

  • M (Machine): Поток операционной системы (OS Thread).
  • P (Processor): Логический процессор, представляет собой контекст для выполнения горутин. Количество P по умолчанию равно количеству ядер ЦП (GOMAXPROCS).
  • G (Goroutine): Сама горутина со своим стеком, который изначально очень мал (~2 КБ) и может расти по мере необходимости.

Планировщик распределяет множество горутин (G) для выполнения на нескольких логических процессорах (P), которые, в свою очередь, работают на небольшом количестве системных потоков (M). Это и есть модель мультиплексирования M:N.

Как это работает:

  1. Горутина запускается с помощью ключевого слова go.
  2. Планировщик ставит её в очередь к одному из логических процессоров P.
  3. Когда горутина выполняет блокирующую операцию (например, чтение из файла, сетевой запрос или ожидание данных из канала), планировщик снимает её с потока M и ставит на её место другую горутину из очереди. Системный поток при этом не блокируется и продолжает работать.

Пример с sync.WaitGroup (правильный способ ожидания):

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    // WaitGroup используется для ожидания завершения всех горутин.
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        // Увеличиваем счетчик горутин на 1.
        wg.Add(1)

        go func(id int) {
            // Уменьшаем счетчик, когда горутина завершается.
            defer wg.Done()
            fmt.Printf("Горутина %d начала работуn", id)
            time.Sleep(100 * time.Millisecond)
            fmt.Printf("Горутина %d завершила работуn", id)
        }(i)
    }

    // Ожидаем, пока счетчик WaitGroup не станет равен нулю.
    wg.Wait()
    fmt.Println("Все горутины завершены.")
}

Основные инструменты для синхронизации:

  • Каналы (chan): Для безопасной передачи данных между горутинами.
  • Пакет sync: Mutex для защиты общих данных, WaitGroup для ожидания завершения группы горутин.