Как ограничить количество *одновременных* запросов к обработчику, используя только стандартные средства Go?

Ответ

Для ограничения количества одновременных (конкурентных) вызовов функции или обработчика в Go, не прибегая к сторонним библиотекам, можно использовать буферизованный канал или семафор.

Это не rate limiting (ограничение частоты), а concurrency limiting (ограничение параллелизма).

1. Использование буферизованного канала

Канал используется как семафор: размер буфера — это максимальное количество одновременных выполнений. Запись в канал "захватывает" слот, чтение — "освобождает".

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.