Підрозділ 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 | Ні | Обмеження паралелізму всередині програми |
Вибір примітива визначається трьома запитаннями:
- Скільки потоків одночасно мають доступ до ресурсу — один або N?
- Потрібна міжпроцесна синхронізація чи лише внутрішньопроцесна?
- Потрібна сигналізація (один потік чекає на подію від іншого) чи лише взаємне виключення?
Для переважної більшості задач — lock і SemaphoreSlim покривають 90% потреб. Monitor — коли потрібна тонка координація через Wait/Pulse. AutoResetEvent — для явного сигналювання між потоками. Mutex і Semaphore — лише коли є реальна потреба у міжпроцесній синхронізації.