Как обеспечить потокобезопасность внутри класса и какие механизмы синхронизации использовать?
Что проверяют
Понимаете ли вы, что потокобезопасность начинается с дизайна класса, а lock лишь один из инструментов. Интервьюер хочет услышать, какой механизм вы возьмёте под какую задачу. Следом часто спрашивают, что делать, если внутри критической секции нужен await.
Как отвечать
Начните с того, как обойтись без синхронизации: неизменяемые объекты (record, init-свойства), отсутствие общего состояния, передача данных через Channel<T>. Если общее изменяемое состояние всё же есть:
Interlocked: атомарныеIncrement,Exchange,CompareExchangeдля одного поля без блокировки. Для счётчика это правильный ответ вместоlock;lock: взаимное исключение внутри процесса по приватномуreadonlyобъекту. Разворачивается вMonitor.EnterиMonitor.Exitвtry/finally. В .NET 9 появился отдельный типSystem.Threading.Lockдля этой роли;Monitor.TryEnterс таймаутом: когда нельзя зависнуть навсегда;ReaderWriterLockSlim: много читателей, редкая запись. На коротких операциях часто проигрывает обычномуlock, проверяйте замером;SemaphoreSlim: ограничение параллелизма и асинхронныйWaitAsync(), поэтому именно он заменяетlockв async-коде;Mutex(именованный): синхронизация между процессами, заметно дороже, потому что идёт через ядро ОС;- конкурентные коллекции:
ConcurrentDictionary,ConcurrentQueue. Атомарна каждая операция, но не их последовательность.
Против deadlock: захватывайте блокировки всегда в одном порядке и не вызывайте внутри критической секции чужой код, события и колбэки.
Где спотыкаются
Пишут await внутри lock. Компилятор это запрещает, а обход через Monitor ломается: продолжение может выполниться в другом потоке, и Monitor.Exit бросит исключение.
Блокируют this, typeof(MyClass) или строку. Такой объект доступен снаружи, и чужой код может захватить ту же блокировку.
Считают, что ConcurrentDictionary решает всё. ContainsKey и следующий за ним Add это две операции, между ними вклинится другой поток. Нужны GetOrAdd или AddOrUpdate, причём фабрика в них может выполниться несколько раз.