Ответ
Горутины — это легковесные потоки, управляемые средой выполнения 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("Все горутины завершили свою работу.")
}