Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Многопоточность.doc
Скачиваний:
4
Добавлен:
28.10.2018
Размер:
225.79 Кб
Скачать

Класс ReaderWriterLock

Использование оператора lock является, наверно, наиболее простым способом синхронизации доступа к ресурсам. Однако это не всегда то, что нам нужно. Рассмотрим классический пример использования общего кэша. Обычно кэш реализуется с использованием класса Dictionary.

private static Dictionary<int, int> m_Cache = new Dictionary<int, int>();

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

Здесь на помощь приходит класс ReaderWriterLock (программа ThreadingReadWriteLocker). Все потоки, обращающиеся к одному ресурсу, должны использовать один экземпляр этого класса. Данный класс имеет методы получения доступа к ресурсу AcquireReaderLock и AcquireWriterLock, а так же соответствующие методы освобождения: ReleaseReaderLock и ReleaseWriterLock.

Работа с классом происходит следующим образом. Поток, который хочет получить доступ только на чтение, вызывает метод AcquireReaderLock. Этому методу в качестве параметра передается время, которое поток должен дожидаться получения соответствующего доступа. Если за это время доступа получить не удалось, то возникает ApplicationException. Вы так же можете передать этому методу параметр Timeout.Infinite. В этом случае ожидание будет длиться бесконечно. Экземпляр класса ReaderWriterLock разрешит доступ только в том случае, если у него есть только запросы на чтение, если же есть хотя бы один запрос на запись, то доступ предоставлен не будет до тех пор, пока все запросы на запись не будут удовлетворены. После завершения чтения поток должен обязательно вызвать метод ReleaseReaderLock. Этим он сигнализирует, что освободил ресурс. Для использования методов AcquireReaderLock и ReleaseReaderLock очень подходит блок try–finally:

try

{

locker.AcquireReaderLock(Timeout.Infinite);

if (m_Cache.ContainsKey(key))

Console.WriteLine( "For key {0} reader value is {1}.", key, m_Cache[key]);

}

finally

{

locker.ReleaseReaderLock();

}

Следует отметить, что класс ReaderWriterLock сам не следит, производите ли вы только чтение или все же изменяете общий ресурс. Такое слежение – задача самого программиста.

Поток, которому нужно производить запись, вызывает метод AcquireWriterLock. Смысл его параметра тот же. Этот метод будет ждать до тех пор, пока не будут удовлетворены все запросы на запись и чтение, которые были сделаны до его вызова. Потом он заблокирует объект до его освобождения методом ReleaseWriterLock. Пока объект заблокирован таким образом, никто не может получть доступ ни на запись, ни на чтение:

try

{

locker.AcquireWriterLock(Timeout.Infinite);

if (m_Cache.ContainsKey(key))

Console.WriteLine("For key {0} reader value is {1}.", key, m_Cache[key]);

else

{

m_Cache[key] = key * key;

Console.WriteLine("For key {0} written value is {1}.", key, m_Cache[key]);

}

}

finally

{

locker.ReleaseWriterLock();

}

Обратите внимание, что после получения доступа на запись поток опять проверяет, есть ли в кэше нужное значение. Это необходимо потому, что пока поток ждал доступа на запись, другой поток, который получил такой доступ ранее, мог уже записать в кэш нужное значение.

Вот, собственно и все, что касается использования класса ReaderWriterLock. Не забывайте освобождать объект после завершения необходимых операций и следите за тем, чтобы не изменять общий ресурс получив доступ только на чтение.