Какие знаешь способы безопасно залочить что-либо внутри класса?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Безопасная синхронизация в 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();
}
}
}
Рекомендации по выбору:
- Для простых сценариев — используйте
lockс приватным объектом синхронизации - Много чтения, мало записи —
ReaderWriterLockSlim - Межпроцессное взаимодействие —
MutexилиSemaphoreс глобальными именами - Ограничение параллелизма —
SemaphoreSlim - Атомарные операции с примитивами —
Interlocked - Потокобезопасные коллекции —
ConcurrentDictionary,ConcurrentQueueи др. - Асинхронный код —
SemaphoreSlimсWaitAsyncили специализированные асинхронные примитивы
Важные принципы безопасности:
- Всегда освобождайте блокировки в
finallyблоках - Избегайте взаимных блокировок (deadlock) — устанавливайте порядок захвата блокировок
- Минимизируйте время удержания блокировки — выполняйте только необходимые операции внутри lock
- Используйте таймауты там, где это возможно (
Monitor.TryEnter,SemaphoreSlim.WaitAsyncс CancellationToken) - Для высоконагруженных сценариев рассматривайте lock-free структуры данных
Правильный выбор механизма синхронизации критически важен для производительности и надежности многопоточных приложений.