Ответ
Для ограничения количества одновременных (конкурентных) вызовов функции или обработчика в 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.
Ответ 18+ 🔞
А, ну ты глянь, какой вопрос подкинули! Ограничить, чтобы не больше N штук одновременно лезло, а не по времени. Это ж элементарно, Ватсон, но народ-то путает, блядь, с rate limiting'ом!
Так, слушай сюда, у меня для тебя два рабочих способа, оба без всяких левых библиотек, на чистом гоёвском коленкоре.
1. Способ для простых пацанов: буферизованный канал, он же семафор
Представь себе очередь в сортир на десять кабинок. Канал — это дверь. Вошёл — занял кабинку (записал в канал), вышел — освободил (прочитал из канала). Если кабинки все заняты — следующий мудак ждёт, пока кто-то не выйдет. Всё, логика кончилась.
// Делаем канал на 10 мест. 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. Способ для заумных: официальный семафор (x/sync/semaphore)
Это если тебе мало каналиков, хочется поумничать. Пакет не стандартный, но официальный, с гитхаба golang. Он даёт больше власти, например, можно не по одному "разрешению" захватывать, а пачками (ну, если запрос тяжёлый).
import (
"context"
"golang.org/x/sync/semaphore"
)
// Создаём семафор с общим "весом" в 10 единиц.
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 — страховка от кривых рук.
defer sem.Release(1)
// Ну и пошла писать губерния...
time.Sleep(2 * time.Second)
w.Write([]byte("Request processed successfully"))
}
Чё выбрать-то? Если задача — "не больше десяти горутин одновременно", то первый способ, канальный, — твой выбор, золотой. Нехуй тут мудрить. Второй — если реально нужны фишки вроде разного "веса" операций или тонкая работа с контекстами. А так-то оба работают, ядрёна вошь!