Ответ
Для ограничения количества одновременных (конкурентных) вызовов функции или обработчика в Go, не прибегая к сторонним библиотекам, можно использовать буферизованный канал или семафор.
Это не rate limiting (ограничение частоты), а concurrency limiting (ограничение параллелизма).
1. Использование буферизованного канала
Канал используется как семафор: размер буфера — это максимальное количество одновременных выполнений. Запись в канал "захватывает" слот, чтение — "освобождает".
// Канал с буфером на 10 элементов.
// Позволит выполняться не более чем 10 обработчикам одновременно.
var sem = make(chan struct{}, 10)
func handler(w http.ResponseWriter, r *http.Request) {
// Попытка записать в канал. Если он полон, горутина заблокируется.
sem <- struct{}{}
// defer гарантирует, что слот будет освобожден после завершения функции.
defer func() { <-sem }()
// Логика обработки запроса
time.Sleep(2 * time.Second)
w.Write([]byte("Request processed successfully"))
}
2. Использование golang.org/x/sync/semaphore
Хотя этот пакет не входит в стандартную библиотеку, он является официальным подрепозиторием Go и предоставляет более гибкий и явный API для семафоров.
import (
"context"
"golang.org/x/sync/semaphore"
)
// Семафор с "весом" 10. Каждый запрос будет "весить" 1.
var sem = semaphore.NewWeighted(10)
func handler(w http.ResponseWriter, r *http.Request) {
// Пытаемся захватить семафор. Учитываем контекст запроса для отмены.
if err := sem.Acquire(r.Context(), 1); err != nil {
// Не удалось захватить (например, контекст отменен)
http.Error(w, "Failed to acquire semaphore", http.StatusServiceUnavailable)
return
}
// Освобождаем семафор после выполнения.
defer sem.Release(1)
// Логика обработки
time.Sleep(2 * time.Second)
w.Write([]byte("Request processed successfully"))
}
Буферизованный канал — простой и идиоматичный способ для базовых случаев. Семафор из x/sync
предоставляет больше контроля, например, возможность захвата с разным "весом" и интеграцию с context
.