OOP Course
Сьогодні

Підрозділ 15.7

Клас Semaphore та SemaphoreSlim

15.7. Клас Semaphore та SemaphoreSlim Mutex і lock реалізують принцип «один доступ одночасно»: лише один потік може захопити ресурс. Але що, якщо ресурс допускає обмежений паралелізм ? Наприклад, в лікарні є 3

15.7. Клас Semaphore та SemaphoreSlim

Mutex і lock реалізують принцип «один доступ одночасно»: лише один потік може захопити ресурс. Але що, якщо ресурс допускає обмежений паралелізм? Наприклад, в лікарні є 3 кабінети УЗД — одночасно можуть прийматися 3 пацієнти, але не 4 і не 10. Або апаратура може одночасно обслуговувати не більше 5 запитів.

Для таких сценаріїв призначений семафор (semaphore). Семафор — це лічильник доступів: він ініціалізується максимальним числом одночасних дозволів і видає їх потокам на вимогу. Потік, що хоче отримати доступ, «забирає» один дозвіл; якщо всі дозволи вичерпані — потік блокується до тих пір, поки якийсь інший потік не «поверне» свій дозвіл. Коли Mutex — це двері з одним місцем, семафор — це стоянка з N місцями.

Semaphore і SemaphoreSlim

У .NET є два варіанти:

  • Semaphore — об'єкт ядра ОС, як і Mutex. Може бути іменованим і доступним між процесами. Повільніший.
  • SemaphoreSlim — легковаговий внутрішньопроцесний семафор. Набагато швидший, підтримує async/await. Є кращим вибором для переважної більшості задач.
Характеристика Semaphore SemaphoreSlim
Область дії Процеси + міжпроцесна Лише один процес
Продуктивність Нижча (ОС-виклики) Висока
async/await Ні Так (WaitAsync)
Іменований Так Ні
Типовий вибір Міжпроцесне обмеження Внутрішньопроцесний ліміт

API: SemaphoreSlim

Метод / конструктор Опис
new SemaphoreSlim(initialCount) Ліміт = initialCount = initialCount доступних дозволів
new SemaphoreSlim(initialCount, maxCount) Початкова кількість і максимальна
Wait() Забирає один дозвіл. Блокує, якщо лічильник = 0
Wait(int ms) З таймаутом. Повертає bool
Release() Повертає один дозвіл (лічильник++)
Release(int count) Повертає count дозволів відразу
CurrentCount Кількість доступних дозволів

Клінічний приклад: кабінети УЗД

У медичному центрі є 3 апарати УЗД. Одночасно можуть проходити дослідження не більше трьох пацієнтів. Якщо всі три апарати зайняті — пацієнти чекають у черзі:

Перші три пацієнти займуть кабінети і розпочнуть дослідження. Четвертий і наступні будуть чекати — Wait() заблокує їх до звільнення місця. Як тільки перший пацієнт викличе Release(), семафор підвищить лічильник і наступний очікуючий потік продовжить виконання.

Release() завжди розміщується у блоці finally — це гарантує звільнення дозволу навіть якщо у тілі потоку виник виняток.

Контроль темпу: обмеження паралельних запитів

Семафор — природний інструмент обмеження навантаження (rate limiting). Наприклад, система відправки SMS-нагадувань не може надсилати більше 5 повідомлень одночасно через обмеження SMS-шлюзу:

Клас Semaphore — іменований, міжпроцесний

Semaphore аналогічний Mutex у контексті міжпроцесного доступу. Його іменована версія дозволяє різним програмам обмежувати спільне використання системного ресурсу:

Release з кількома дозволами

SemaphoreSlim.Release(int count) дозволяє повернути кілька дозволів за раз. Це корисно, коли один «виробник» генерує пакет завдань і хоче одразу дозволити кільком споживачам почати роботу:

Ініціалізація SemaphoreSlim(0, 10) означає: початково 0 дозволів (всі потоки зупиняться на Wait), максимум 10. Після Release(5) лічильник стає 5, і всі 5 очікуючих потоків одночасно пробуджуються.

Загальна картина: коли який примітив обирати

Завершимо розділ зведеною таблицею всіх вивчених примітивів синхронізації:

Примітив Максимум потоків Між процесами Основний сценарій
lock 1 Ні Захист критичної секції (найчастіший вибір)
Lock (.NET 9) 1 Ні Як lock, з кращою продуктивністю
Monitor 1 Ні Wait/Pulse для координації виробника і споживача
AutoResetEvent 1 (по черзі) Ні Сигналізація одному потоку про готовність даних
Mutex 1 Так Singleton-процес, захист системного файлу
Semaphore N Так Обмеження доступу до N ресурсів між процесами
SemaphoreSlim N Ні Обмеження паралелізму всередині програми

Вибір примітива визначається трьома запитаннями:

  1. Скільки потоків одночасно мають доступ до ресурсу — один або N?
  2. Потрібна міжпроцесна синхронізація чи лише внутрішньопроцесна?
  3. Потрібна сигналізація (один потік чекає на подію від іншого) чи лише взаємне виключення?

Для переважної більшості задач — lock і SemaphoreSlim покривають 90% потреб. Monitor — коли потрібна тонка координація через Wait/Pulse. AutoResetEvent — для явного сигналювання між потоками. Mutex і Semaphore — лише коли є реальна потреба у міжпроцесній синхронізації.

Розроблено Tomka Yurii · © 2026 ·