Ответ
Семафор — это примитив синхронизации, который ограничивает количество горутин, имеющих одновременный доступ к общему ресурсу или выполняющих определенный участок кода. По сути, это счетчик с ограниченной емкостью.
Основные способы реализации в Go:
-
Через буферизованный канал (классический способ): Канал используется как контейнер для "разрешений". Отправка в канал — захват разрешения, чтение из канала — освобождение.
import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup // Семафор, ограничивающий параллельное выполнение до 3 горутин sem := make(chan struct{}, 3) for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { sem <- struct{}{} // Захватываем "слот" defer func() { <-sem // Освобождаем "слот" wg.Done() }() // Выполняем "тяжелую" работу fmt.Printf("Работает горутина #%dn", i) time.Sleep(time.Second) }(i) } wg.Wait() // Ждем завершения всех горутин }
-
С помощью пакета
golang.org/x/sync/semaphore
(рекомендуемый способ): Этот пакет предоставляет более явный и функциональный API.import ( "context" "golang.org/x/sync/semaphore" "sync" ) func main() { var wg sync.WaitGroup // Семафор с весом 3 sem := semaphore.NewWeighted(3) ctx := context.Background() for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() // Захватываем ресурс. Блокируется, если все ресурсы заняты. if err := sem.Acquire(ctx, 1); err != nil { // Обработка ошибки (например, если контекст отменен) return } defer sem.Release(1) // Освобождаем ресурс // ... работа ... }(i) } wg.Wait() }
Основные сценарии использования:
- Ограничение количества одновременных HTTP-запросов к внешнему API.
- Ограничение числа одновременных подключений к базе данных.
- Контроль параллелизма при выполнении ресурсоемких задач (например, обработка файлов, вычисления).