Ответ
Semaphore — это примитив синхронизации, который ограничивает количество потоков, которые могут одновременно получить доступ к общему ресурсу или пулу ресурсов. В отличие от lock/Monitor, который является мьютексом (пропускает только один поток), семафор может пропускать заданное максимальное количество потоков одновременно.
Основные понятия:
- Счётчик — внутренний счётчик семафора. При создании задаётся его начальное и максимальное значение.
WaitOne()— поток вызывает этот метод, чтобы "войти" в семафор. Если счётчик > 0, он уменьшается на 1, и поток продолжает работу. Если счётчик = 0, поток блокируется до тех пор, пока другой поток не вызоветRelease().Release()— поток, закончивший работу с ресурсом, вызывает этот метод. Счётчик увеличивается на 1, и один из ожидающих потоков (если есть) разблокируется.
Пример: Ограничение доступа к пулу из 3-х подключений к БД.
using System.Threading;
public class DatabaseConnectionPool
{
// Семафор, разрешающий максимум 3 одновременных доступа
private static Semaphore _pool = new Semaphore(initialCount: 3, maximumCount: 3);
public void AccessDatabase(int threadId)
{
Console.WriteLine($"Поток {threadId} ждёт доступа...");
_pool.WaitOne(); // Уменьшаем счётчик. Если счётчик = 0 — ждём.
try
{
Console.WriteLine($"Поток {threadId} вошёл в критическую секцию.");
// Имитация работы с БД
Thread.Sleep(2000);
}
finally
{
// Release() ВСЕГДА должен быть в finally-блоке, чтобы гарантировать освобождение семафора даже при исключении.
_pool.Release(); // Увеличиваем счётчик, разрешая вход другому потоку.
Console.WriteLine($"Поток {threadId} покинул критическую секцию.");
}
}
}
// Запуск 10 потоков, но одновременно работать будут не более 3-х.
Важные варианты и детали:
-
SemaphoreSlim— легковесная версия для синхронизации внутри одного процесса. Рекомендуется к использованию вместоSemaphoreдля внутрипроцессных сценариев, так как он эффективнее. Поддерживает асинхронное ожидание (WaitAsync).private static SemaphoreSlim _asyncPool = new SemaphoreSlim(3, 3); // ... await _asyncPool.WaitAsync(); // Асинхронное ожидание без блокировки потока. try { /* работа */ } finally { _asyncPool.Release(); } -
Именованные семафоры (
Semaphore) — могут использоваться для синхронизации между разными процессами (межпроцессное взаимодействие).// Процесс 1 и Процесс 2 будут использовать один и тот же системный семафор. Semaphore crossProcessSemaphore = new Semaphore(1, 1, "GlobalMyAppSemaphore");
Типичные сценарии использования: ограничение числа одновременных HTTP-запросов, управление доступом к пулу ограниченных ресурсов (подключения, порты), реализация "производитель-потребитель" с ограниченной ёмкостью буфера.
Видео-ответы
▶
▶
▶
▶
▶