← Назад к вопросам

Что такое SemaphoreSlim и чем он отличается от Semaphore? Приведите пример использования.?

2.3 Middle🔥 142 комментариев
#Базы данных и SQL

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

SemaphoreSlim vs Semaphore: общая концепция

Оба класса реализуют семафор — примитив синхронизации для управления доступом к ограниченному количеству ресурсов. Семафор поддерживает счётчик, указывающий количество доступных разрешений. Потоки могут захватывать (wait) разрешение (уменьшая счётчик) и освобождать (release) его (увеличивая счётчик). Если счётчик равен нулю, поток блокируется до появления свободного разрешения.

Ключевые различия

АспектSemaphoreSemaphoreSlim
Пространство имёнSystem.ThreadingSystem.Threading
Тип семафораЯдерный объект ОС (межпроцессный).User-mode с быстрым путем, резервно использует ядерные объекты.
ПроизводительностьМедленнее, требует переключения в режим ядра.Быстрее, особенно когда нет конкуренции.
ИменованиеПоддерживает именованные семафоры для синхронизации между процессами.Только для синхронизации в рамках одного процесса.
Асинхронная поддержкаНет встроенной поддержки async/await.Есть метод WaitAsync() для асинхронного ожидания.
Максимальное значениеЗадаётся при создании (до int.MaxValue).int.MaxValue, но рекомендуется для небольшого числа параллельных операций.
Дополнительные функцииМетоды CurrentCount, AvailableWaitHandle (с осторожностью).

Основной вывод: SemaphoreSlim — это облегчённая, высокопроизводительная версия для синхронизации потоков внутри одного процесса с поддержкой асинхронности. Semaphore используется реже, в основном для межпроцессной синхронизации.

Пример использования SemaphoreSlim

Типичный сценарий — ограничение количества одновременных запросов к внешнему API или базы данных.

using System;
using System.Threading;
using System.Threading.Tasks;

public class RateLimitedApiClient
{
    // Семафор, разрешающий не более 3 одновременных вызовов
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3, 3);
    
    public async Task<string> CallApiAsync(int requestId)
    {
        // Асинхронно ждём разрешения (не блокируя поток)
        await _semaphore.WaitAsync();
        
        try
        {
            Console.WriteLine($"Запрос {requestId} начал выполнение. Доступно слотов: {_semaphore.CurrentCount}");
            
            // Имитация долгой работы (например, HTTP-запрос)
            await Task.Delay(2000);
            
            Console.WriteLine($"Запрос {requestId} завершён.");
            return $"Результат для {requestId}";
        }
        finally
        {
            // Всегда освобождаем семафор!
            _semaphore.Release();
        }
    }
}

public class Program
{
    public static async Task Main()
    {
        var client = new RateLimitedApiClient();
        var tasks = new Task<string>[10];
        
        // Запускаем 10 "параллельных" задач
        for (int i = 0; i < 10; i++)
        {
            tasks[i] = client.CallApiAsync(i);
        }
        
        // Ждём завершения всех
        var results = await Task.WhenAll(tasks);
        Console.WriteLine("Все запросы обработаны.");
    }
}

Пояснение к примеру:

  1. new SemaphoreSlim(3, 3) — создаёт семафор с начальным и максимальным значением счётчика 3.
  2. WaitAsync() — асинхронно ожидает доступного разрешения. Если CurrentCount > 0, поток продолжает выполнение без блокировки.
  3. CurrentCount — свойство, показывающее текущее количество доступных разрешений (для мониторинга/отладки).
  4. Release() — освобождает разрешение, увеличивая счётчик. Критично вызывать в finally-блоке, чтобы избежать утечки семафора (ситуации, когда разрешение никогда не вернётся).

Важные нюансы:

  • Не используйте AvailableWaitHandle у SemaphoreSlim без крайней необходимости — это ядерный объект, создание которого снижает производительность.
  • Для синхронного ожидания используйте Wait(), но в асинхронном коде предпочтительнее WaitAsync().
  • SemaphoreSlim не потокобезопасен для Dispose() — убедитесь, что никто не использует семафор при вызове Dispose().
  • В отличие от Semaphore, SemaphoreSlim не позволяет задавать имя и не может использоваться для синхронизации между процессами.

Когда выбирать:

  • SemaphoreSlim — в 95% случаев для ограничения параллелизма внутри приложения (запросы, операции ввода-вывода, работа с пулом).
  • Semaphore — только когда требуется синхронизация нескольких независимых процессов (например, два разных .NET-приложения, работающих с общим ресурсом).