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

Какие знаешь способы безопасно залочить что-либо внутри класса?

2.0 Middle🔥 181 комментариев
#Асинхронность и многопоточность

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

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

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

Безопасная синхронизация в C#

В C# существует несколько способов безопасной синхронизации доступа к общим ресурсам внутри класса. Выбор конкретного механизма зависит от сценария использования, требований к производительности и сложности логики.

1. Блокировка через lock (Monitor)

Наиболее распространенный и простой способ для внутрипроцессной синхронизации.

public class ThreadSafeCache
{
    private readonly object _lockObject = new object();
    private Dictionary<string, object> _cache = new Dictionary<string, object>();
    
    public void Add(string key, object value)
    {
        lock (_lockObject)
        {
            _cache[key] = value;
        }
    }
    
    public bool TryGet(string key, out object value)
    {
        lock (_lockObject)
        {
            return _cache.TryGetValue(key, out value);
        }
    }
}

Ключевые моменты:

  • Используйте приватный объект для блокировки (_lockObject), а не публичные объекты или this
  • lock компилируется в Monitor.Enter/Monitor.Exit с обработкой исключений
  • Подходит для коротких операций, так как может вызвать contention (состязание)

2. ReaderWriterLockSlim

Оптимизированная блокировка для сценариев "много читателей, мало писателей".

public class ConcurrentRepository
{
    private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
    private List<string> _data = new List<string>();
    
    public string Get(int index)
    {
        _rwLock.EnterReadLock();
        try
        {
            return _data[index];
        }
        finally
        {
            _rwLock.ExitReadLock();
        }
    }
    
    public void Add(string item)
    {
        _rwLock.EnterWriteLock();
        try
        {
            _data.Add(item);
        }
        finally
        {
            _rwLock.ExitWriteLock();
        }
    }
}

Преимущества:

  • Параллельное чтение несколькими потоками
  • Эксклюзивная запись одним потоком
  • Поддерживает апгрейд из read-lock в write-lock

3. Мьютексы (Mutex)

Для межпроцессной синхронизации, когда нужно координировать доступ между разными процессами.

public class CrossProcessResource
{
    private Mutex _mutex = new Mutex(false, "Global\\MySharedMutex");
    
    public void AccessSharedResource()
    {
        _mutex.WaitOne();
        try
        {
            // Работа с разделяемым ресурсом между процессами
        }
        finally
        {
            _mutex.ReleaseMutex();
        }
    }
}

4. Семафоры (Semaphore, SemaphoreSlim)

Ограничивают количество одновременных доступов к ресурсу.

public class ConnectionPool
{
    private SemaphoreSlim _semaphore = new SemaphoreSlim(5, 5); // Макс 5 одновременных подключений
    private List<Connection> _connections = new List<Connection>();
    
    public async Task<Connection> GetConnectionAsync()
    {
        await _semaphore.WaitAsync();
        try
        {
            // Получение или создание подключения
            return _connections.FirstOrDefault(c => c.IsFree);
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

SemaphoreSlim легче и быстрее для внутрипроцессных сценариев, поддерживает асинхронное ожидание.

5. Interlocked операции

Атомарные операции для простых типов без полной блокировки.

public class Counter
{
    private int _count = 0;
    
    public void Increment()
    {
        Interlocked.Increment(ref _count);
    }
    
    public bool TryUpdate(int newValue, int comparand)
    {
        return Interlocked.CompareExchange(ref _count, newValue, comparand) == comparand;
    }
}

Применение: счетчики, флаги, простые обновления значений.

6. Concurrent коллекции

Специализированные потокобезопасные коллекции из System.Collections.Concurrent.

public class ConcurrentProcessor
{
    private ConcurrentDictionary<string, Data> _cache = new ConcurrentDictionary<string, Data>();
    private ConcurrentQueue<Request> _requests = new ConcurrentQueue<Request>();
    
    public void Process()
    {
        // Не требуется явная синхронизация
        _cache.TryAdd("key", new Data());
        
        if (_requests.TryDequeue(out var request))
        {
            // Обработка запроса
        }
    }
}

Достоинства: высокая производительность за счет lock-free алгоритмов в большинстве операций.

7. Асинхронные примитивы

Для асинхронного кода используйте SemaphoreSlim, AsyncLock (из сторонних библиотек) или Channel для producer-consumer сценариев.

public class AsyncResourceManager
{
    private SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);
    
    public async Task<int> CalculateAsync()
    {
        await _asyncLock.WaitAsync();
        try
        {
            await Task.Delay(100); // Асинхронная работа
            return 42;
        }
        finally
        {
            _asyncLock.Release();
        }
    }
}

Рекомендации по выбору:

  1. Для простых сценариев — используйте lock с приватным объектом синхронизации
  2. Много чтения, мало записиReaderWriterLockSlim
  3. Межпроцессное взаимодействиеMutex или Semaphore с глобальными именами
  4. Ограничение параллелизмаSemaphoreSlim
  5. Атомарные операции с примитивамиInterlocked
  6. Потокобезопасные коллекцииConcurrentDictionary, ConcurrentQueue и др.
  7. Асинхронный кодSemaphoreSlim с WaitAsync или специализированные асинхронные примитивы

Важные принципы безопасности:

  • Всегда освобождайте блокировки в finally блоках
  • Избегайте взаимных блокировок (deadlock) — устанавливайте порядок захвата блокировок
  • Минимизируйте время удержания блокировки — выполняйте только необходимые операции внутри lock
  • Используйте таймауты там, где это возможно (Monitor.TryEnter, SemaphoreSlim.WaitAsync с CancellationToken)
  • Для высоконагруженных сценариев рассматривайте lock-free структуры данных

Правильный выбор механизма синхронизации критически важен для производительности и надежности многопоточных приложений.

Какие знаешь способы безопасно залочить что-либо внутри класса? | PrepBro