ReaderWriterLock

Обычно экземпляры типов потокобезопасны при параллельных операциях чтения, но не при параллельных обновлениях, или параллельном чтении и обновлении. То же самое справедливо для ресурсов, например файлов. Если в основном выполняется чтение и только изредка – запись, защита экземпляров таких типов простой эксклюзивной блокировкой для всех режимов доступа может необоснованно ограничить параллелизм. В качестве примера можно привести сервер приложений, в котором часто используемые данные кэшируются в статических полях для быстрого доступа. Класс ReaderWriterLock разработан специально под такой сценарий работы.

ReaderWriterLock предоставляет отдельные методы для блокировки на чтение и на запись – AcquireReaderLock и AcquireWriterLock. Оба метода принимают аргумент-таймаут и генерируют исключение ApplicationException, если этот таймаут истекает (вместо возвращения false, как это делают остальные аналогичные методы, связанные с потоками). Таймаут может быть легко превышен, если ресурс пользуется популярностью.

Блокировка снимается при помощи методов ReleaseReaderLock или ReleaseWriterLock. Эти методы поддерживают вложенные блокировки. Предоставляется также метод ReleaseLock, снимающий все вложенные блокировки за один вызов. (Далее можно вызвать RestoreLock для восстановления состояния всех блокировок, предшествовавшего вызову ReleaseLock – в подражание поведению Monitor.Wait).

Можно начать с блокировки на чтение вызовом AcquireReaderLock, затем превратить ее в блокировку на запись, используя UpgradeToWriterLock. Этот метод возвращает cookieдля последующего вызова DowngradeFromWriterLock. Такая система позволяет читателю запрашивать временный доступ для записи без необходимости повторного ожидания в очереди.

В следующем примере стартуют четыре потока – один непрерывно добавляет элементы в список, другой их удаляет, а два оставшихся постоянно сообщают о количестве элементов в списке. Первые два устанавливают блокировку на запись, два оставшихся – только на чтение. При каждой блокировке используется таймаут в 10 секунд (обработка исключений в данном примере опущена для краткости).

class Program { static ReaderWriterLock rw = new ReaderWriterLock(); static List<int> items = new List<int>(); static Random rand = new Random(); static void Main(string[] args) { new Thread(delegate() { while (true) AppendItem(); }).Start(); new Thread(delegate() { while (true) RemoveItem(); }).Start(); new Thread(delegate() { while (true) WriteTotal(); }).Start(); new Thread(delegate() { while (true) WriteTotal(); }).Start(); } static int GetRandNum(int max) { lock (rand) return rand.Next(max); } static void WriteTotal() { rw.AcquireReaderLock(10000); int tot = 0; foreach (int i in items) tot += i; Console.WriteLine(tot); rw.ReleaseReaderLock(); } static void AppendItem() { rw.AcquireWriterLock(10000); items.Add(GetRandNum(1000)); Thread.SpinWait(400); rw.ReleaseWriterLock(); } static void RemoveItem() { rw.AcquireWriterLock(10000); if (items.Count > 0) items.RemoveAt(GetRandNum(items.Count)); rw.ReleaseWriterLock(); } }

Поскольку добавление элементов в список происходит быстрее, чем удаление, в этом примере в метод AppendItem добавлен вызов SpinWait для сохранения баланса.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: