Ответ
Безопасная блокировка (синхронизация) доступа к общему ресурсу из нескольких потоков — ключевая задача. В C# для этого есть несколько механизмов, выбор зависит от контекста.
Основные подходы:
-
Ключевое слово
lock(Monitor.Enter/Exit)- Самый распространенный и простой способ для синхронизации доступа к блоку кода внутри одного процесса (AppDomain).
- Принцип: Использует объект-маркер (
object _lock = new object();). Только один поток может владеть этим маркером в данный момент. -
Важно: Используйте приватный, readonly объект, специально созданный для блокировки. Никогда не используйте
this,typeof(...)или публичные объекты — это может привести к взаимоблокировкам (deadlock).public class Account { private readonly object _balanceLock = new object(); private decimal _balance; public void Deposit(decimal amount) { // Только один поток может выполнять этот блок кода для данного экземпляра Account lock (_balanceLock) { _balance += amount; } } }
-
Класс
Mutex(мьютекс)- Принцип: Аналогичен
lock, но может быть именованным и использоваться для синхронизации между разными процессами (межпроцессная синхронизация). - Минусы: Более тяжеловесный, чем
lock.private static Mutex _mutex = new Mutex(false, "GlobalMyAppMutex"); public void Process() { _mutex.WaitOne(); // Захват мьютекса try { // Критическая секция } finally { _mutex.ReleaseMutex(); // Освобождение мьютекса ВСЕГДА в finally } }
- Принцип: Аналогичен
-
Класс
SemaphoreиSemaphoreSlim- Принцип: Позволяет ограничить количество потоков, которые могут одновременно войти в критическую секцию, до заданного числа (больше 1).
SemaphoreSlim— легковесная версия для внутрипроцессной синхронизации. - Идеально для: Ограничения доступа к пулу ресурсов (например, не более 10 одновременных подключений к внешнему API).
private static SemaphoreSlim _pool = new SemaphoreSlim(initialCount: 3, maxCount: 3);
public async Task AccessResourceAsync() { await _pool.WaitAsync(); // Ждем, если уже 3 потока внутри try { // Работа с ресурсом (максимум 3 потока одновременно) await Task.Delay(1000); } finally { _pool.Release(); // Освобождаем слот } }
- Принцип: Позволяет ограничить количество потоков, которые могут одновременно войти в критическую секцию, до заданного числа (больше 1).
-
Класс
ReaderWriterLockSlim- Принцип: Оптимизирован для сценариев, где чтение происходит часто, а запись редко. Позволяет множеству потоков читать данные одновременно, но дает эксклюзивный доступ на запись.
- Идеально для: Кэшей или конфигураций, которые часто читаются, но изредка обновляются.
private readonly ReaderWriterLockSlim _cacheLock = new ReaderWriterLockSlim(); private Dictionary<string, Data> _cache = new();
public Data GetData(string key) { _cacheLock.EnterReadLock(); // Множественные потоки могут войти сюда try { return _cache[key]; } finally { _cacheLock.ExitReadLock(); } }
public void UpdateData(string key, Data value) { _cacheLock.EnterWriteLock(); // Эксклюзивная блокировка. Ждет, пока все читатели выйдут. try { _cache[key] = value; } finally { _cacheLock.ExitWriteLock(); } }
Общее правило: Всегда заключайте работу с блокировкой в блок try...finally, чтобы гарантировать ее освобождение даже в случае исключения. Для асинхронных методов предпочитайте SemaphoreSlim.WaitAsync() или другие асинхронные примитивы, чтобы не блокировать потоки пула.