Что такое ReaderWriterLock и для чего он нужен
Что проверяют
Понимаете ли вы, зачем нужна блокировка, различающая чтение и запись, и хватает ли трезвости не тащить её туда, где хватает обычного lock. Вопрос быстро выводит на цену синхронизации.
Как отвечать
Идея простая: читателей можно пускать одновременно, потому что они ничего не меняют, а писателя только одного и в одиночестве. Выигрыш появляется, когда чтений заметно больше и они не мгновенные.
В .NET для этого берут ReaderWriterLockSlim; старый ReaderWriterLock оставлен ради совместимости, в новом коде его не используют.
private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion);
public int Read(string key)
{
_lock.EnterReadLock();
try { return _cache[key]; }
finally { _lock.ExitReadLock(); }
}Режимов три: чтение, запись и обновляемое чтение (EnterUpgradeableReadLock). Последний закрывает схему посмотреть и при необходимости изменить: обычный читатель, отпустив блокировку ради записи, теряет гарантию, что состояние не поменялось. Обновляемую блокировку держит только один поток, и читателям она при этом не мешает.
Уместно назвать альтернативы: lock для коротких секций, ConcurrentDictionary и неизменяемые коллекции с заменой ссылки там, где данные можно скопировать, SemaphoreSlim для асинхронного кода.
Где спотыкаются
Берут ReaderWriterLockSlim по умолчанию. Она дороже монитора, и на коротких операциях обычный lock её обгоняет: разделение ролей окупается только при долгом чтении и редкой записи.
Удерживают её через await. Блокировка привязана к потоку, а продолжение после ожидания выполнится на другом, и освобождение упадёт исключением. Асинхронный участок закрывают семафором.
Забывают finally и политику рекурсии. Исключение внутри секции навсегда оставляет блокировку захваченной, а повторный вход в том же потоке при настройке без рекурсии сразу даёт LockRecursionException.