Ответ
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-запросов, управление доступом к пулу ограниченных ресурсов (подключения, порты), реализация "производитель-потребитель" с ограниченной ёмкостью буфера.
Ответ 18+ 🔞
Ну ты представляешь, семафор — это такая штука, которая как швейцар в переполненный клуб пускает. Не одного, как обычный лок, а сразу пачку ребят, но не больше, чем стульев внутри. То есть, грубо говоря, это ограничитель потока потоков, если по-простому.
Суть в двух словах:
- Счётчик — у семафора внутри табло, как в бане: «Свободно мест: N». При создании задаёшь, сколько всего мест и сколько свободно сразу.
WaitOne()— это когда поток подходит к турникету и говорит: «Пустите». Если место есть — счётчик уменьшается, и он проходит. Если мест ноль — стоит и ждёт, пока кто-то не выйдет.Release()— это когда поток, наконец-то, отъел своё и вываливается из клуба. Счётчик увеличивается, и следующий ждущий может зайти.
Пример: У нас есть три стула в баре для подключений к базе, а желающих — десять.
using System.Threading;
public class DatabaseConnectionPool
{
// Швейцар-семафор: максимум трое внутри, и сразу трое пустых мест.
private static Semaphore _pool = new Semaphore(initialCount: 3, maximumCount: 3);
public void AccessDatabase(int threadId)
{
Console.WriteLine($"Поток {threadId} тусуется у входа и курит...");
_pool.WaitOne(); // Пытается пройти. Если мест нет — зависнет тут.
try
{
Console.WriteLine($"Поток {threadId} зашёл, занял стул и орёт: 'Мне кофе!'");
// Делает вид, что работает с базой
Thread.Sleep(2000);
}
finally
{
// Release() ВСЕГДА пихай в finally! А то вдруг исключение выскочит, поток сдохнет, а место так и не освободит — все остальные потом до скончания века ждать будут, ебать колотить.
_pool.Release(); // Выполз, освободил стул.
Console.WriteLine($"Поток {threadId} отдал стул и поплёлся домой.");
}
}
}
// Запустишь 10 потоков — одновременно внутри будут только 3, остальные как лохи на улице мёрзнут.
Важные нюансы, чтобы не обосраться:
-
SemaphoreSlim— это как облегчённая версия семафора, для своих, внутри одного приложения. Он быстрее и современнее. А главное — умеет в асинхронность без блокировок, что сейчас вообще святое.private static SemaphoreSlim _asyncPool = new SemaphoreSlim(3, 3); // ... await _asyncPool.WaitAsync(); // Не блокирует поток, просто асинхронно ждёт своей очереди. Красота! try { /* делаем дела */ } finally { _asyncPool.Release(); } -
Именованные семафоры (
Semaphore) — это уже тяжёлая артиллерия, когда нужно синхронизировать не потоки в одной программе, а разные, ебись оно конём, процессы на компе. Например, чтобы два экземпляра твоего софта не перегрызлись за один ресурс.// Создаётся системный семафор, видимый всем. Если имя совпадёт — процессы будут использовать один и тот же. Semaphore crossProcessSemaphore = new Semaphore(1, 1, "Global\MyAppSemaphore");
Где это обычно применяют: чтобы не отправить сто пятьсот HTTP-запросов одновременно и не положить сервер, чтобы ограничить подключения к базе, или сделать очередь с ограниченной вместимостью, где производители не заваливают потребителей работой под завязку. В общем, полезная хуйня, когда нужно впустить, но не всех сразу.
Видео-ответы
▶
▶
▶
▶
▶