Ответ
Выбор механизма синхронизации зависит от конкретной задачи. Вот основные подходы, которые я применяю на практике:
1. lock (ключевое слово для Monitor)
Используется для простых сценариев синхронизации внутри одного процесса.
private readonly object _syncRoot = new object();
lock (_syncRoot)
{
// Критическая секция. Например, изменение общего списка.
_sharedList.Add(item);
}
Почему lock? Это самый простой и производительный способ для большинства случаев. Важно использовать выделенный приватный объект (_syncRoot), а не this или тип, чтобы избежать внешних deadlock.
2. SemaphoreSlim
Идеален для ограничения количества потоков, одновременно получающих доступ к ресурсу, особенно в асинхронном коде.
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5); // Макс 5 потоков
await _semaphore.WaitAsync();
try
{
await AccessLimitedResourceAsync();
}
finally
{
_semaphore.Release();
}
Почему SemaphoreSlim? Он легче, чем классический Semaphore, и имеет асинхронный API (WaitAsync), что предотвращает блокировку потоков пула.
3. ReaderWriterLockSlim
Применяется для оптимизации сценариев «много читателей, редко писатель».
private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
// Чтение (множественный доступ)
_rwLock.EnterReadLock();
try
{
return _cache.Get(key);
}
finally
{
_rwLock.ExitReadLock();
}
// Запись (эксклюзивный доступ)
_rwLock.EnterWriteLock();
try
{
_cache.Set(key, value);
}
finally
{
_rwLock.ExitWriteLock();
}
Почему ReaderWriterLockSlim? Он значительно повышает производительность, когда операции чтения преобладают над операциями записи.
4. Mutex
Нужен для координации между процессами (например, чтобы гарантировать запуск только одного экземпляра приложения).
bool createdNew;
using var mutex = new Mutex(true, "Global\MyAppMutex", out createdNew);
if (!createdNew)
{
// Другой экземпляр приложения уже запущен.
return;
}
// Продолжаем работу...
Ключевые принципы, которых я придерживаюсь:
- Всегда освобождайте блокировки в
finallyдля гарантии избежания deadlock. - Минимизируйте время удержания блокировки — выполняйте внутри критической секции только необходимые операции.
- Избегайте вложенных блокировок или строго соблюдайте единый порядок их захвата.
- Для асинхронного кода предпочитайте
SemaphoreSlim,async/awaitи конкурентные коллекции (ConcurrentBag,ConcurrentDictionary) вместо ручной синхронизации там, где это возможно.