Параллелизм в Go легко выходит из-под контроля. Запустил 1000 горутин и сервер задыхается. Семафор ограничивает количество горутин, которые работают одновременно и сервер становится более стабильным.
Что такое семафор и почему в Go его нет
В других языках семафор встроен в стандартную библиотеку. В Go его нет, но есть буферизованный канал и он делает ту же работу.
Логика простая: ёмкость канала = максимальное число параллельных горутин. Хотите запустить горутину — отправьте в канал пустую структуру. Нет свободного места — ждите. Горутина завершилась — прочитайте из канала, освободите слот.
Базовая реализация:
sem := make(chan struct{}, 10) // максимум 10 горутин одновременно
for _, task := range tasks {
sem <- struct{}{} // занять слот (блокирует, если канал полон)
go func(t Task) {
defer func() { <-sem }() // освободить слот при выходе
process(t)
}(task)
}
// Ждём завершения всех горутин
for i := 0; i < cap(sem); i++ {
sem <- struct{}{}
}У
struct{} нулевой размер в памяти. Канал здесь нужен только для сигнализации, а не для передачи данных. Это чисто ибез лишних зависимостей.Где это ломается
Базовый вариант работает для скриптов и одноразовых утилит. Но у него есть жёсткое ограничение: он не знает про отмену контекста.
Если приложение получило сигнал завершения или истёк дедлайн, горутины, заблокированные на
sem <- struct{}{}, просто продолжают ждать. Бесконечно. Приложение не завершится корректно.Для чего-то серьёзнее скрипта нужен
select с ctx.Done().Версия с отменой контекста:
func acquire(ctx context.Context, sem chan struct{}) error {
select {
case sem <- struct{}{}:
return nil
case <-ctx.Done():
return ctx.Err()
}
}Теперь при отмене контекста горутина не зависнет, она получит ошибку и завершится.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoToProduction