Что такое семафор в программировании — и почему пачки запросов медленнее

Тебе надо отправить 100 запросов к API. Все сразу нельзя — сервер ответит ошибкой 429. Поэтому ты делишь их на пачки по пять: отправил пять, дождался, отправил следующие пять.
Работает. Но медленно. Почему? В каждой пятёрке есть один медленный запрос на 5 секунд. Остальные четыре закончились за секунду — и стоят, ждут его. Двадцать пачек по 5 секунд — 100 секунд.
А можно по-другому: держать в работе ровно пять запросов в любой момент. Один закончился — сразу стартует следующий. Те же 100 запросов, тот же лимит — около 40 секунд. Штука, которая так делает, называется семафор.
Что такое семафор
Семафор — это счётчик свободных мест. Он пускает к ресурсу не больше N задач одновременно. Остальные ждут в очереди.
Представь парковку на пять мест со шлагбаумом. Пока есть место — шлагбаум открыт. Все пять заняты — следующая машина ждёт. Одна уехала — одна въехала. Парковка никогда не пустует, если есть очередь. И никогда не переполнена.
Придумал семафоры Эдсгер Дейкстра в 1960-х. Тогда это было про процессы в операционной системе. Сейчас — про запросы, файлы и подключения к базе.
У семафора есть родственник — мьютекс. Мьютекс пускает строго одного. По сути это семафор на одно место.
Как он работает
У семафора две операции:
- Взять место. Если счётчик больше нуля — уменьши его и проходи. Если ноль — жди.
- Вернуть место. Увеличь счётчик и разбуди первого в очереди.
Вот и всё. В JavaScript семафор для запросов пишется меньше чем в двадцать строк:
function createLimit(max) {
let active = 0
const queue = []
const next = () => {
if (active >= max || queue.length === 0) return
active++
const { task, resolve, reject } = queue.shift()
task().then(resolve, reject).finally(() => {
active--
next()
})
}
return (task) => new Promise((resolve, reject) => {
queue.push({ task, resolve, reject })
next()
})
}
А пользоваться им так:
const limit = createLimit(5)
const results = await Promise.all(
urls.map((url) => limit(() => fetch(url)))
)
Обрати внимание на () => fetch(url). Мы передаём не запрос, а функцию, которая его запустит. Если написать просто limit(fetch(url)), все 100 запросов улетят сразу, и семафор будет охранять пустоту. Это самая частая ошибка.
Не хочешь писать сам — есть готовая библиотека p-limit. Работает так же.
Почему это важно тебе
Почти любой внешний сервис ограничивает частоту запросов. Подробно — в статье что такое rate limit. Отправишь всё через Promise.all — получишь 429. Будешь слать по одному — прождёшь вечность. Семафор — золотая середина.
Особенно это видно с ИИ. Ты прогоняешь через модель 300 описаний товаров. У API лимит на число одновременных запросов. Семафор на 5–10 мест — и скрипт идёт на полной скорости, не упираясь в ошибки.
И это не только про сеть. Ты обрабатываешь тысячу картинок — каждая съедает память. Запустишь все разом — упадёшь с нехваткой памяти. Семафор на четыре — и процессор занят, а память в порядке.
Если для тебя await и промисы пока туманны, начни со статьи что такое асинхронность.
Где ты уже встречал семафор
Ты им пользуешься каждый день, просто не знаешь.
- Браузер. По старому протоколу HTTP/1.1 Chrome держит не больше шести соединений с одним сайтом. Седьмая картинка ждёт, пока освободится место. Это семафор на шесть.
- База данных. У PostgreSQL по умолчанию 100 подключений. Сто первое получит ошибку
sorry, too many clients already. Пул подключений — тот же семафор: он раздаёт соединения по очереди. - Python и Go. В Python есть готовый
asyncio.Semaphore. В Go роль семафора играет буферизованный канал.
Сколько мест ставить в семафоре?
Начни с того, что пишет документация API. Если там тишина — 5. Ловишь 429 — уменьши. Всё быстро и без ошибок — попробуй 10.
Семафор защищает от гонок?
Только семафор на одно место — то есть мьютекс. Если мест пять, пять задач могут одновременно менять одни и те же данные. Тогда гонка никуда не денется.
Что будет, если задача упадёт с ошибкой?
В коде выше место вернётся всё равно — за это отвечает finally. Без него одна ошибка навсегда «съедала» бы место, и рано или поздно очередь встала бы намертво.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





