Общее представление о горутинах

Легковесные и эффективные горутины — одна из жемчужин Golang. Благодаря им разработчики запросто пишут программы с конкурентным и параллельным выполнением. Но неправильное использование горутин чревато проблемами: утечками памяти, снижением производительности и даже сбоем рабочих серверов.
Что такое «горутина»?
Горутина — легковесный поток, управляемый средой выполнения Go. Потоки же подобны рабочим, которыми выполняются задачи в приложении. Но традиционные потоки обычно ресурсоемки, а горутины очень легкие. Такая эффективность достигается планированием и динамическим управлением средой выполнения Go своей памятью.
Здесь прослеживается аналогия с рестораном. Традиционные потоки — это как если бы на каждое блюдо приходилось по одному шеф-повару. Мощно, но дорого. Горутины же, как помощники повара, берутся за большое количество блюд. Когда горутины не задействованы, их обязанности возвращаются управляющему — среде выполнения Go.
Ключевые особенности горутин
- Конкурентность и параллелизм: задачи выполняются горутинами конкурентно и параллельно. В первом случае выполнение не обязательно одновременное, во втором — одновременное, на многоядерных процессорах.
- Экономичность: при запуске на горутину расходуется куда меньше памяти, чем на поток — около 2 Кб.
- Динамический рост: стеки горутин при необходимости увеличиваются и сжимаются, потоками же выделяется фиксированный размер стека.
Базовая реализация

Горутины начинают использовать так:
package main
import (
"fmt"
"time"
)
func printMessage(message string) {
for i := 1; i <= 5; i++ {
fmt.Println(message, i)
time.Sleep(500 * time.Millisecond)
}
}
func main() {
go printMessage("Hello from Goroutine") // Запуск горутины
printMessage("Hello from Main") // Выполнение в основном потоке
}
Рекомендации по использованию горутин
1. Избежание утечек горутин
Утечка горутины случается, когда горутина остается запущенной неопределенное время или в ожидании условия, которое так и не осуществляется. Это чревато потреблением системных ресурсов и в итоге сбоем сервера.
Пример: утечками горутины чревато незакрытие канала или неопределенное ожидание в операторе
select.
Решение: использование defer и корректная очистка:
package main
import (
"fmt"
"time"
)
func worker(done chan bool) {
defer close(done) // Обеспечивается очистка ресурсов
fmt.Println("Starting work...")
time.Sleep(2 * time.Second)
fmt.Println("Work done!")
done <- true
}
func main() {
done := make(chan bool)
go worker(done)
// Ожидание завершения горутины
<-done
}
2. Разумная синхронизация
Для координирования горутин или совместного использования ими данных используются примитивы синхронизации из пакета sync: WaitGroup и Mutex.
Пример: использование sync.WaitGroup:
package main
import (
"fmt"
"sync"
)
func task(id int, wg *sync.WaitGroup) {
defer wg.Done() // Когда горутина завершается, счетчик уменьшается
fmt.Printf("Task %d is running\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1) // Счетчик увеличивается для каждой горутины
go task(i, &wg)
}
wg.Wait() // Блокируется до завершения всех горутин
fmt.Println("All tasks completed")
}
3. Корректное обращение с паниками
Паника в горутине чревата сбоем всей программы, только если распространяется на основную горутину. Чтобы корректно справиться с паникой внутри горутин, применяется recover.
Пример: восстановление после паники:
package main
import (
"fmt"
)
func safeTask() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("Something went wrong!") // Моделирование паники
}
func main() {
go safeTask()
fmt.Println("Main function continues...")
}
Когда используются горутины
- При выполнении задач с интенсивным вводом-выводом: горутины отлично справляются с такими задачами, как запросы к базе данных, считывания файлов или API-вызовы.
- При параллельной обработке: задачи с интенсивным расходом ресурсов процессора распараллеливаются применением многоядерных систем.
- В приложениях реального времени: например, для обработки тысяч конкурентно выполняемых веб-сокет-подключений.
Когда горутины не используются
- Для небольших, скоротечных задач: создание горутины здесь чревато накладными расходами. В крошечных задачах быстрее встроенное выполнение.
- Когда важен порядок: с горутинами проявляется недетерминированное поведение. Их избегают, если последовательность задач критична.
- Без корректной синхронизации: совместное использование данных в горутинах без синхронизации чревато состояниями гонок.
Реальный пример: опасность утечек горутин
В системе обработки платежей для каждой транзакции создается горутина. Если одна горутина застрянет в ожидании сетевого ответа, который так и не будет получен, произойдет утечка памяти. Со временем эти утечки разрастаются, производительность снижается, в итоге система аварийно завершается.
Рекомендация: всегда задавайте тайм-ауты для операций. Для эффективного управления временем жизни горутины используйте
context.WithTimeout.
Заключение
Горутины — мощный инструмент Go, но применять их нужно с умом. Благодаря пониманию нюансов горутин создаются надежные, высокопроизводительные приложения. Всегда очищайте ресурсы, корректно справляйтесь с паникой и синхронизируйте общие данные.
При правильном использовании горутин приложения становятся масштабируемыми, эффективными, сопровождать такие приложения — одно удовольствие.
Читайте также:
- Шаблон «Стратегия» на Go
- Синхронизация данных в реальном времени между MongoDB и Elasticsearch на Golang
- Защита бэкенда на Go: шифрование, предотвращение уязвимостей и не только
Читайте нас в Telegram, VK и Дзен
Перевод статьи Adityasinghrathore: How to Use Goroutines the Right Way Basis to Advance (explained simply)





