Ответ
Выбор механизма синхронизации зависит от конкретной задачи. Вот основные подходы, которые я применяю на практике:
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) вместо ручной синхронизации там, где это возможно.
Ответ 18+ 🔞
А, синхронизация, говоришь? Ну это ж, блядь, святое дело, если хочешь чтобы твоё приложение не разъехалось по швам как пьяный дед на льду. Слушай сюда, я тебе сейчас по полочкам разложу, как я с этим добром обращаюсь.
1. lock — наш бронежилет обыкновенный
Это как дверь в сортир на вечеринке захлопнуть. Пока один там свои дела делает, остальные ждут снаружи, терпят. Используется для простых дел, внутри одного процесса.
private readonly object _syncRoot = new object(); // Вот этот самый чурбачок
lock (_syncRoot)
{
// Тут твоя святая святых. Только один поток в этот момент.
_sharedList.Add(item); // Добавил и не обосрался
}
А нахуя он? Да потому что проще и быстрее него нихуя нет для большинства случаев. Главное — бери свой личный объект (_syncRoot), а не херачь lock(this). Иначе придут соседи, тоже захотят в твой сортир, и будет deadlock — все поссать хотят, а дверь не открывается.
2. SemaphoreSlim — турникет в метро
Представь: есть узкий проход, и ты говоришь — «окей, больше пяти человек одновременно не пускаю». Идеально, когда нужно ограничить потоки, особенно если они асинхронные и не хочется блокировать всё нахуй.
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5); // Пять — и хватит!
await _semaphore.WaitAsync(); // Ждём своей очереди как культурные
try
{
await AccessLimitedResourceAsync(); // Делаем дела
}
finally
{
_semaphore.Release(); // Выходим, даём место другим. Обязательно!
}
А нахуя он? Он легче своего старшего брата Semaphore и умеет ждать асинхронно (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(); // Вышел, теперь можно снова читать
}
А нахуя он? Когда чтения дохуя, а записи раз в год, он даёт овердохуищный прирост производительности. Не заставляй всех стоять в одной очереди из-за одного писаки.
4. Mutex — межгалактический семафор
Это когда нужно договориться не только между потоками в одной программе, а между разными процессами, а то и между компами. Классика — чтобы второй экземпляр твоего приложения не запустился.
bool createdNew;
using var mutex = new Mutex(true, "Global\MyAppMutex", out createdNew);
if (!createdNew)
{
// Ага, сука, кто-то уже сидит в этом кресле!
return; // Сворачиваемся и идём нахуй.
}
// Мы первые, работаем спокойно...
А теперь, блядь, главные правила, которые надо выжечь на жопе:
- Освобождай блокировки в
finallyвсегда, даже если тебе кажется, что исключения не будет. Иначе залочишь навек — и приехали. - Сиди в критической секции как можно меньше. Зашёл, быстро сделал дело, вышел. Не надо там чай пить и с коллегами болтать.
- Вложенные блокировки — это ад. Если без них нихуя, то договаривайся о строгом порядке захвата. Всегда сначала замок А, потом Б. Иначе два потока встанут в позу «ты мне — я тебе» и будут ждать вечность.
- Для асинхронного кода —
SemaphoreSlimи всякиеConcurrentBag,ConcurrentDictionaryтвои лучшие друзья. Они заточены под это дело. Не городи велосипед сlockв асинхронных методах, получишь боль и страдание.
Вот так вот, коротко и без воды. Выбирай инструмент по задаче, а не потому что он первый в гугле всплыл.