Какие примитивы синхронизации кроме lock и monitor используются в C#
Что проверяют
Понимаете ли вы, что lock закрывает только простой случай: один поток внутри секции, всё в пределах процесса, без await. Следом обычно спрашивают, чем защитить код с await внутри и чем SemaphoreSlim отличается от Semaphore.
Как отвечать
Сгруппируйте примитивы по задаче, а не выдавайте списком:
Interlocked: атомарныеIncrement,Add,Exchange,CompareExchangeдля счётчиков и флагов, без блокировки вовсе;SemaphoreSlim: ограничивает число одновременных входов и умеетWaitAsync.new SemaphoreSlim(1, 1)это стандартный асинхронный мьютекс;Mutex: объект ядра ОС, может быть именованным и работает между процессами. Привязан к потоку: освобождать его должен тот же поток, поэтому черезawaitего не протащить;Semaphore: тяжёлый вариант на объекте ядра, нужен ради именованного межпроцессного сценария;AutoResetEvent,ManualResetEventSlim: сигнал «можно продолжать». Auto пропускает один поток и сам закрывается, Manual остаётся открытым доReset();CountdownEvent,Barrier: дождаться N участников или синхронизировать фазы работы;ReaderWriterLockSlim: много читателей или один писатель, асинхронного API не имеет;SpinLock,SpinWait: для очень коротких секций, где усыплять поток дороже, чем покрутиться.
Хорошо добавить, что с .NET 9 есть отдельный тип System.Threading.Lock, и lock по нему компилируется в более лёгкий код, чем через Monitor. А часть задач лучше решать вовсе без замков: ConcurrentDictionary, Channel<T> для очереди производитель-потребитель.
Где спотыкаются
Пытаются написать await внутри lock. Компилятор это запрещает, а обход через Monitor или Mutex ломается: продолжение после await может выполниться на другом потоке, и освобождение бросит исключение.
Берут Mutex там, где хватило бы lock или SemaphoreSlim. Каждый вызов уходит в ядро ОС, и на горячем пути это заметно медленнее.
Забывают Release() в finally. Одно исключение внутри секции, и семафор навсегда теряет слот, а через какое-то время приложение встаёт в ожидании.