В чем ключевые преимущества горутины перед потоком операционной системы (OS thread)?

Ответ

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


  • Легковесность: Стек горутины изначально занимает всего ~2 КБ, в то время как стек потока ОС — 1-2 МБ. Это позволяет создавать сотни тысяч и даже миллионы горутин в одном процессе без значительной нагрузки на память.



  • Низкие затраты на переключение контекста: Переключение между горутинами происходит в пространстве пользователя (user-space) и управляется планировщиком Go. Это намного быстрее, чем переключение потоков ОС, которое требует дорогостоящего вызова ядра (kernel call).



  • Управление рантаймом (Планировщик Go): Go использует модель планирования M:N, где M горутин распределяются на N потоков ОС. Планировщик Go интеллектуально управляет этим распределением, например, если горутина блокируется на системном вызове, планировщик может переключить другую горутину на выполнение в том же потоке ОС.



  • Идиоматичная коммуникация через каналы: В Go для синхронизации и обмена данными между горутинами принято использовать каналы. Это делает код более безопасным и понятным по сравнению с традиционным подходом с общей памятью и мьютексами, который часто используется при работе с потоками ОС.


Пример с sync.WaitGroup для корректного ожидания завершения горутин:

package main

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

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

    for i := 0; i < 5; i++ {
        // Увеличиваем счетчик WaitGroup на 1 перед запуском горутины.
        wg.Add(1)

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

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