TGViewer
Библиотека Go-разработчика | Golang Библиотека Go-разработчика | Golang @goproglib · 24.1K subscribers
Post #7093 3.13K
🧑‍💻 Семафор для прода

В прошлый раз мы разобрали, как буферизованный канал работает как семафор. Это хороший паттерн, но у него есть потолок: нет встроенной поддержки контекста, нет возможности задать разный «вес» для разных операций. Пакет golang.org/x/sync закрывает оба этих пробела.

Что внутри

Пакет предоставляет взвешенный семафор semaphore.NewWeighted. Принцип тот же: ограничиваете число параллельных операций. Но теперь каждая операция может занимать не один слот, а несколько. И Acquire умеет слушать контекст.

Базовое использование:
import "golang.org/x/sync/semaphore"

var (
maxWorkers = int64(10)
sem = semaphore.NewWeighted(maxWorkers)
)

func processAll(ctx context.Context, tasks []Task) error {
for _, task := range tasks {
if err := sem.Acquire(ctx, 1); err != nil {
return fmt.Errorf("semaphore acquire: %w", err)
}

go func(t Task) {
defer sem.Release(1)
process(ctx, t)
}(task)
}

// Ждём завершения всех горутин
if err := sem.Acquire(ctx, maxWorkers); err != nil {
return fmt.Errorf("final acquire: %w", err)
}
sem.Release(maxWorkers)
return nil
}


Два момента, на которые стоит обратить внимание.

Acquire возвращает ошибку, если контекст отменён или истёк дедлайн. Горутины не зависают — они получают сигнал и завершаются. Это именно то, чего не хватало буферизованному каналу.

Финальный Acquire(ctx, maxWorkers) — это трюк для ожидания. Мы пробуем занять все слоты сразу. Это возможно только тогда, когда все горутины завершились и освободили свои слоты. Как только захват прошёл — сразу отпускаем и выходим.

Взвешенный захват: зачем он нужен

Не все задачи одинаково нагружают систему. Загрузка большого файла потребляет куда больше ресурсов, чем быстрый запрос в базу.

Взвешенный семафор моделирует напрямую:
// Тяжёлая операция занимает 3 слота
if err := sem.Acquire(ctx, 3); err != nil {
return err
}
go func() {
defer sem.Release(3)
heavyOperation(ctx)
}()

// Лёгкая операция занимает 1 слот
if err := sem.Acquire(ctx, 1); err != nil {
return err
}
go func() {
defer sem.Release(1)
lightOperation(ctx)
}()


При лимите в 10 слотов одновременно могут работать либо 10 лёгких операций, либо 3 тяжёлых, либо любая комбинация. Вы управляете не просто количеством горутин, а их суммарным потреблением ресурсов.

Когда использовать

Буферизованный канал подходит для скриптов и простых утилит, где все задачи равнозначны и контекст не нужен. golang.org/x/sync/semaphore — выбор для продакшена: корректно обрабатывает отмену, не теряет горутины при завершении и позволяет точнее контролировать нагрузку.

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoToProduction
  • 👍 16
  • 🥱 1
More from @goproglib
  1. Sep 30, 2026🧑‍💻 Эмулятор AWS-сервисов Kumo — это небольшой инструмент на Go для локальной имитации A…
  2. Sep 29, 2026👨‍💻 Библиотека для написания LSP-серверов Написать свой Language Server с нуля на Go сло…
  3. Sep 28, 2026🤔 Вопрос с собеседования по Go Что выведет программа? ❤️ — 1 true / 0 false 🔥 — 1 true /…
  4. Sep 28, 2026👩‍💻 Что на самом деле происходит внутри Go map? После Go 1.24 обычный map внутри работае…
  5. Sep 26, 2026🔥 В Go 1.27 появился portable SIMD До этого SIMD-оптимизации в Go требовали архитектурног…
  6. Sep 25, 2026🤡🤡 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека Go-разработчика #GoGiggle
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →