Горутины: Основы конкурентности
В большинстве языков программирования (Java, C++, Python) при необходимости выполнять несколько задач одновременно используются потоки операционной системы (OS Threads). В Go есть горутины (goroutines). Это не одно и то же.
1. Что такое горутина?
Горутина — это легковесный поток исполнения, управляемый планировщиком среды выполнения (runtime) Go, а не операционной системой.
- Минимальный размер: Горутина начинается с крошечного стека (всего 2 КБ), который при необходимости динамически растет и уменьшается. Потоки ОС имеют фиксированный, большой размер стека (обычно 1–2 МБ).
- Скорость запуска: Горутину можно запустить за доли микросекунды. Запуск потока ОС — это тяжелая операция, требующая обращения к ядру системы.
- Масштабируемость: На обычном ноутбуке можно легко запустить десятки и сотни тысяч горутин. Попытка создать столько же потоков ОС приведет к падению программы или зависанию системы.
2. Как запустить горутину?
Всё просто: добавьте ключевое слово go перед вызовом любой функции.
func task(name string) {
for i := 1; i <= 3; i++ {
fmt.Printf("Задача %s: итерация %d\n", name, i)
time.Sleep(100 * time.Millisecond) // Имитация работы
}
}
func main() {
// Обычный вызов
// task("A")
// Запуск в горутине
go task("A")
go task("B")
// Важно: если main завершится, все горутины убьются мгновенно!
time.Sleep(1 * time.Second)
fmt.Println("Программа завершена.")
}
Критически важный момент: Программа на Go завершается, как только заканчивает работу функция main. Горутины не "ждут" своего завершения, если main уже вышел. Поэтому в примере выше мы используем time.Sleep (в реальных задачах для этого используют WaitGroup.
3. Планировщик Go (Go Scheduler)
Почему это работает так быстро? Потому что у Go есть собственный планировщик (M:N).
- M горутин (из вашей программы) планировщик распределяет по N потокам ОС (обычно количество потоков ОС равно количеству ядер вашего процессора).
- Если одна горутина «засыпает» (например, ждет ответ от базы данных), планировщик мгновенно переключает поток ОС на другую горутину, которая готова работать.
- Это переключение происходит в пространстве пользователя, без необходимости переходить в «режим ядра» ОС. Это и дает фантастическую скорость.
4. Конкурентность vs Параллелизм
Это часто путают, но разница важна:
- Конкурентность (Concurrency): Это когда у вас есть структура программы, позволяющая выполнять несколько задач независимо (но не обязательно одновременно). Горутины дают именно конкурентность.
- Параллелизм (Parallelism): Это когда задачи выполняются физически одновременно на разных ядрах процессора.
Горутины позволяют Go автоматически использовать все ядра вашего процессора для параллельного выполнения задач. Вам не нужно вручную распределять работу по ядрам — планировщик Go делает это за вас.
Итог: главные правила
- Не делайте предположений о порядке: Горутины запускаются и выполняются в непредсказуемом порядке. Не пишите код, который зависит от того, какая горутина выполнится первой.
go— это не магия: Каждая горутина потребляет память и процессорное время. Не запускайте их там, где хватило бы простого цикла, если данных очень мало.- Ожидайте завершения: Никогда не оставляйте горутины «в подвешенном состоянии». Если они что-то делают,
mainдолжен знать, когда они закончили.