Ответ
Семафор — это примитив синхронизации, который используется для ограничения доступа к общему ресурсу со стороны нескольких потоков (в Go — горутин). Его основная идея — это счетчик, отслеживающий количество свободных слотов для доступа к ресурсу.
В отличие от мьютекса, который предоставляет эксклюзивный доступ (его можно считать семафором со значением 1), семафор позволяет работать с ресурсом одновременно N горутинам.
Реализация в Go
В Go семафоры удобно и идиоматично реализуются с помощью буферизованного канала. Емкость канала выступает в роли счетчика семафора.
- Захват (
acquire
): Отправка значения в канал. Если канал полон (все слоты заняты), горутина блокируется. - Освобождение (
release
): Чтение значения из канала. Это освобождает место для другой ожидающей горутины.
Пример:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
// Создаем семафор, который разрешает не более 3 одновременных операций
sem := make(chan struct{}, 3)
var wg sync.WaitGroup
// Запускаем 10 горутин, которые хотят выполнить работу
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
sem <- struct{}{} // Захватываем слот семафора
defer func() { <-sem }() // Освобождаем слот после завершения
// Выполняем "тяжелую" работу
fmt.Printf("Горутина %d начала работуn", id)
time.Sleep(2 * time.Second)
fmt.Printf("Горутина %d закончила работуn", id)
}(i)
}
wg.Wait()
}
Основные применения:
- Ограничение параллелизма (Rate Limiting): Контроль количества одновременных запросов к внешнему API.
- Управление пулом ресурсов: Ограничение числа активных соединений с базой данных.
- Предотвращение перегрузки системы: Не давать ресурсоемким задачам выполняться всем сразу, чтобы не исчерпать CPU или память.