Ответ
Семафор в Go — это примитив синхронизации, который позволяет ограничить количество горутин, одновременно обращающихся к общему ресурсу или выполняющих определенный участок кода.
В стандартной библиотеке Go нет встроенной реализации семафора, но она доступна в популярном пакете golang.org/x/sync/semaphore
.
Ключевой особенностью является semaphore.Weighted
, который позволяет каждой операции "захватывать" (Acquire
) и "освобождать" (Release
) определенный "вес". Это делает его гибким инструментом для управления ресурсами.
Основные сценарии применения:
- Ограничение параллелизма: Контроль количества одновременных HTTP-запросов к внешнему API, чтобы не превысить rate limit.
- Управление пулом ресурсов: Ограничение числа активных подключений к базе данных.
- Контроль ресурсоемких задач: Ограничение количества горутин, выполняющих тяжелые вычисления (например, обработка видео), чтобы избежать перегрузки CPU.
- Реализация пула воркеров (Worker Pool): Семафор может определять максимальное количество одновременно работающих воркеров.
Пример использования semaphore.Weighted
:
package main
import (
"context"
"fmt"
"log"
"runtime"
"time"
"golang.org/x/sync/semaphore"
)
func main() {
// Создаем семафор с максимальным весом, равным количеству ядер CPU.
// Это ограничит количество одновременно выполняемых "тяжелых" задач.
maxWorkers := runtime.NumCPU()
sem := semaphore.NewWeighted(int64(maxWorkers))
ctx := context.Background()
// Запускаем 20 задач, но одновременно будут выполняться не более `maxWorkers`.
for i := 0; i < 20; i++ {
// Захватываем "вес" 1. Если все ресурсы заняты, горутина блокируется здесь.
if err := sem.Acquire(ctx, 1); err != nil {
log.Printf("Failed to acquire semaphore: %v", err)
break
}
go func(taskID int) {
// Освобождаем ресурс в конце выполнения, чтобы другая горутина могла его занять.
defer sem.Release(1)
fmt.Printf("Задача #%d: выполняется работа...n", taskID)
time.Sleep(2 * time.Second) // Имитация тяжелой работы
fmt.Printf("Задача #%d: работа завершена.n", taskID)
}(i)
}
// Ждем, пока все горутины освободят семафор.
if err := sem.Acquire(ctx, int64(maxWorkers)); err != nil {
log.Printf("Failed to acquire all semaphore weights: %v", err)
}
fmt.Println("Все задачи выполнены.")
}
Важно отличать семафор от мьютекса: мьютекс — это частный случай семафора с весом 1, предназначенный для обеспечения эксклюзивного доступа к ресурсу.