В чём разница между Semaphore и SemaphoreSlim в .NET?

Ответ

Semaphore и SemaphoreSlim — это примитивы синхронизации, ограничивающие число потоков, которые могут одновременно получить доступ к ресурсу или пулу. Их ключевое отличие — в области применения и производительности.

SemaphoreSlim (рекомендуется для внутрипроцессной синхронизации)

  • Назначение: Легковесная версия, оптимизированная для сценариев в рамках одного процесса.
  • Производительность: Высокая, так как реализована в пользовательском режиме (user-mode) и обращается к ядру ОС только при блокировке.
  • Ключевая особенность: Поддерживает асинхронное ожидание через метод WaitAsync(), что критически важно для современных async/await паттернов.
  • Межпроцессность: Не поддерживает именованные семафоры, работает только внутри процесса.
// Пример: ограничение параллельных HTTP-запросов
private static readonly SemaphoreSlim _throttler = new SemaphoreSlim(5); // Макс 5 параллельных задач

public async Task<string> MakeThrottledRequestAsync(string url)
{
    await _throttler.WaitAsync(); // Асинхронное ожидание разрешения
    try
    {
        using var client = new HttpClient();
        return await client.GetStringAsync(url);
    }
    finally
    {
        _throttler.Release(); // Освобождение слота
    }
}

Semaphore (для сложных сценариев)

  • Назначение: Класс-обёртка над объектом ядра ОС Windows. Используется для межпроцессной синхронизации или когда нужны функции, специфичные для ядра.
  • Производительность: Ниже, чем у SemaphoreSlim, так как каждый вызов WaitOne()/Release() требует переключения в режим ядра.
  • Ключевая особенность: Поддерживает именованные семафоры, которые могут синхронизировать потоки разных процессов.
  • Асинхронность: Не имеет нативных асинхронных методов.

// Пример: межпроцессная синхронизация доступа к общему файлу
// Имя семафора должно быть уникальным в системе
using var semaphore = new Semaphore(1, 1, "Global\MyApp_FileAccessSemaphore");

semaphore.WaitOne(); // Синхронная блокировка (может быть долгой)
try
{
    // Критическая секция, доступная только одному процессу за раз
    File.AppendAllText("shared.log", $"{DateTime.UtcNow}: Запись из процесса {Process.GetCurrentProcess().Id}n");
}
finally

### Практическое правило
*   **Всегда начинайте с `SemaphoreSlim`**, если синхронизация нужна только внутри вашего приложения.
*   Используйте `Semaphore` только в двух случаях:
    1.  Требуется синхронизация между несколькими независимыми процессами.
    2.  Необходимо время ожидания, превышающее возможности `SemaphoreSlim` (что маловероятно).

Ответ 18+ 🔞

Давай разберёмся с этими двумя штуками, а то в документации как обычно — вода водой, а на деле всё просто, как три копейки.

Вот представь: у тебя есть туалет в офисе. Одноместный. И есть очередь из желающих. Это — lock или Mutex. Но что, если у тебя туалетов несколько? Допустим, три кабинки. Вот тут-то и появляются наши герои — Semaphore и SemaphoreSlim. Они и говорят: "Ребята, одновременно может быть только трое, остальные — ждите своей очереди".

А теперь, блядь, в чём разница между ними? Всё просто, как палка.

SemaphoreSlim (Лёгкий и быстрый, для своих)

Это как внутренний туалет для сотрудников. Быстрый, удобный, все в одном здании.

  • Для чего: Сугубо внутри одного приложения. Не лезет к ядру операционки без крайней нужды, поэтому быстрее.
  • Главный козырь: Умеет нормально работать с async/await! У него есть метод WaitAsync(). Это пиздец как важно в современных асинхронных приложениях. Хочешь ограничить, сколько одновременно твоих асинхронных запросов к API летит — это твой выбор.
  • Между процессами: Не умеет. Только в рамках своего процесса.

Вот смотри, как им пользоваться, чтобы не накосячить:

// Допустим, больше 5 запросов одновременно к API не пускаем
private static readonly SemaphoreSlim _throttler = new SemaphoreSlim(5);

public async Task<string> GetDataAsync(string url)
{
    // Тут мы НЕ блокируем поток! Ждём асинхронно.
    await _throttler.WaitAsync();
    try
    {
        using var client = new HttpClient();
        return await client.GetStringAsync(url);
    }
    finally
    {
        // Выходим из кабинки — освобождаем место. finally — чтобы даже при ошибке не забыть.
        _throttler.Release();
    }
}

Видишь? Красиво, асинхронно, без простоев потоков. SemaphoreSlim — это обычно то, что нужно в 95% случаев.

Semaphore (Тяжёлая артиллерия, для межпроцессного общения)

А это уже как общественный туалет на вокзале. Им могут пользоваться совершенно разные, ни о чём не подозревающие друг о друге процессы.

  • Для чего: Для синхронизации между разными .exe-шниками. Обёртка над системным объектом ядра Windows. Медленнее, потому что каждый заход — это обращение к ядру.
  • Главный козырь: Именованные семафоры. Можешь дать ему имя, и другой процесс сможет найти его по этому имени и встать в ту же очередь.
  • Асинхронность: Забудь. У него только синхронное ожидание WaitOne(). Может надолго заблокировать твой поток.

Пример, когда он нужен:

// Два разных приложения пишут в один лог-файл. Чтобы не было каши, синхронизируемся.
// Имя "Global\" — чтобы видно было всем.
using var semaphore = new Semaphore(1, 1, "Global\MySuperLogFileSemaphore");

semaphore.WaitOne(); // Стоим и ждём, пока освободится. Поток спит.
try
{
    // Пишем в файл. В этот момент никакой другой процесс не сможет сюда зайти.
    File.AppendAllText("shared.log", $"Писал процесс {Process.GetCurrentProcess().Id}n");
}
finally
{
    semaphore.Release(); // Отпускаем следующего страдальца.
}

Так что же выбрать? Коротко и на пальцах:

  1. Работаешь в одном приложении (в рамках одного процесса) и нужна асинхронность?SemaphoreSlim, даже не думай. Это твоя рабочая лошадка.
  2. Нужно координировать работу двух отдельных программ (процессов) на одном компе? — Вот тогда тащи Semaphore с именем. Другого нормального способа нет.

Начинай всегда с SemaphoreSlim. Про Semaphore вспоминай только тогда, когда тебе в голову придёт бредовая идея вроде "а дай-ка я своё приложение в два экземпляра запущу, и чтобы они друг другу не мешали". Вот для такого бреда он и создан.