Главная » C# » Реализация потоковой архитектуры "читатель/писатель" в Visual C# (Sharp)

0

Потокова я архитектур а "читатель/писатель " основан а н а идее ,  чт о  в  т о  время ,  когд а оди н пото к выполняе т операци ю чтения , а друго й пото к такж е хоче т считат ь т е  ж е данные , т о почем у б ы ем у  н е позволит ь это ?  Н о есл и  оди н  пото к хоче т выполнят ь операци ю  записи ,  т о  тольк о   это т   пото к   може т  делат ь   это .   Иным и   словами ,   досту п к   данны м   дл я   чтени я    могу т   разделят ь   нескольк о   потоков ,    н о   дл я   запис и   досту п к  данны м   предоставляетс я   исключительн о  одном у   потоку .

В .NE T реализован-клас с System.Threading.ReaderWriterLock, содержащи й фуНК- циональност ь   "читатель/писатель" .   Это т   клас с   подобе н   класс у  Monitor в   том ,   чт о о н   предоставляе т   средств о  дл я   управлени я   доступо м   к  данным ,   н о   н е   определяет , к каки м данны м осуществляетс я доступ . Клас с ReaderWriterLock имее т  нескольк о методо в  и  свойств ,  самы е  важны е  и з  которы х  перечислен ы   в  табл .   13.1 .

Таблица     13.1.     Основные      методы      класса      ReaderWriterLock

Методы

Описание

AcquireReaderLock()

Получает  блокировку читателя.  Блокировку читателя могут  получать  несколько  потоков  одновременно

AcquireWriterLock()

Получает блокировку писателя. Только один  поток мет удерживать  блокировку  писателя

DowngradeFromWriterLock()

Преобразует блокировку писателя  в  блокировку читатя.  Использование этого метода  позволяет избежать последовательного  вызова  методов ReleaseWriterLock() И AcquireReaderLock()

UpgradeToWriterLock()

Преобразует блокировку читателя  в блокировку  писатя.  Использование этого  метода  позволяет избежать последовательного  вызова  методов ReleaseReaderLock() И AcquireWriterLock()

ReleaseLock()

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

ReleaseReaderLock()

Уменьшает на единицу значение счета  блокировки  чателя.  Чтобы  полностью освободить  блокировку чителя,  необходимо,  чтобы  количество вызовов  метода ReleaseReaderLock ()  было равно количеству вызов  метода  AcquireReaderLock!)

ReleaseWriterLock()

Уменьшает на  единицу значение счета блокировки  пателя.  Чтобы  полностью освободить  блокировку писеля,  необходимо, чтобы  количество вызовов метода ReleaseWriterLock ()   было равно количеству вызов  метода  AcquireWriterLock()

Тепер ь рассмотри м  приме р  обработк и   коллекци и   четырьм я  потоками :   трем я читателям и и одни м писателем . В пример е  стратегическ и используетс я мето д Thread.sieepo, чтоб ы можн о был о видеть , каки м образо м  поток и  читател я  и  пи – сател я   взаимодействую т дру г  с  другом .

using System.Threading;

ReaderWriterLock rwlock = new ReaderWriterLock();

List<int> elements = new List<int>(); elements.Add(10);

elements.Add(20);

Thread threadl = new Thread(

() => {

Thread.Sleep(lOOO); Console.WriteLine(

"Thread 1 waiting for read lock"); rwlock.AcquireReaderLock(-l); Console.WriteLine("Thread 1 has read lock"); foreach (int item in elements) {

Console.WriteLine("Thread 1 Item(" +

item + ")") ;

Thread.Sleep(1000);

}) ;

Console.WriteLine(

"Thread 1 releasing read lock");

rwlock.ReleaseLock();

Thread thread2 = new Thread(

О   =>  {

Thread.Sleep(1250); Console.WriteLine(

"Thread 2 waiting for read lock"); rwlock.AcquireReaderLock(-l); Console.WriteLine("Thread 2 has read lock"); foreach (int item in elements) {

Console.WriteLine("Thread 2 Item (" +

item + ") ") ;

Thread.Sleep(lOOO);

Console.WriteLine(

"Thread 2 releasing read lock");

rwlock. ReleaseI>ock() ;

}) ;   •

Thread thread3 = new Threadl

0  =>  {

Thread.Sleep(1750); Console.WriteLine(

"Thread 3 waiting for read lock"); rwlock.AcquireReaderLock(-1); Console.WriteLine("Thread 3 has read lock"); foreach (int item in elements) {

Console.WriteLine("Thread 3 Item (" +

item + ") ") ; Thread.Sleep(1000);

Console.WriteLine(

"Thread 3 releasing read lock");

rwlock.ReleaseLockO;

})

Thread thread4 = new Thread(

0  =>  {

Thread.Sleep(1500); Console.WriteLine(

"Thread 4 waiting for write lock"); rwlock.AcquireWriterLock(-1); Console.WriteLine("Thread 4 has write Lock"); elements.Add(30);

Console.WriteLine(

"Thread 4 releasing write lock");

rwlock.Releaseliock() ;

}) ;

threadl.Start(); thread2.Start(); threads.Start(); thread4.Start();

Жирным  шрифтом  в  предыдущем  коде  выделены  все  обращения  к  реализации класса читателя/писателя .NET. В отличие от ключевого слова lock и типа Monitor,

для типа ReaderWriterLock создается экземпляр, который совместно используется потоками.

Методам для получения блокировки читателя и писателя передается параметр -1, означающий,  что  метод  должен  ожидать,  пока  не  будет  получена  блокировка. В качестве параметра методу может передаваться положительное значение, указающее тайм-аут в миллисекундах, по истечению которого,  если  блокировка  не была получена, метод возвращает управление вызывающему потоку. При исполовании тайм-аута, прежде чем пытаться манипулировать разделяемым кодом, необходимо проверить свойства IsReaderLockHeld или IsWriterLockHeld, чтобы убедиться в том, что блокировка была получена. После получения блокировки чателя потоки читателей обрабатывают элементы коллекции в цикле.

ПРИМЕЧАНИЕ

Данный пример, кажется, нарушает правило, гласящее  удерживать  блокировку  как можно короткое время, т. к. в нем блокировка удерживается во время обработки элентов в цикле. В случае с реализацией читателя/писателя мы имеем уникальную суацию в  том, что он предназначается для манипулирования  данными, которые в  оовном только считываются. Это означает, что большую часть времени разделяемые данные будут считаться доступными только для чтения. А для редких и коротких иервалов времени,  когда  в  разделяемые данные  выполняется  запись,  ожидание  пота момент-другой не является ничем серьезным. Использовать блокировку читатя/писателя для  данных,  в  основном  не  являющихся только для  чтения,  нет  смысла. В  таких  ситуациях  следует  использовать  подход  с типом  Monitor, как  было  описано в  предыдущем  разделе.

Данный пример демонстрирует  обработку данных, предназначенных в  основном для чтения, т. к. в нем используются три потока для считывания и один для записи. Важно, чтобы во время удерживания блокировки чтения не выполнялось операций записи. Данная реализация читателя/писателя предназначена служить в качестве руководства, но не обязательно следовать только методике, изложенной в этом роводстве.

Результаты исполнения кода будут следующими:

Thread 1 waiting for read lock // Поток 1 ожидает блокировку чтения Thread 1 has read lock   // Поток 1 получил блокировку чтения Thread 1 Item (10)

Thread 2 waiting for read lock Thread 2 has read lock

Thread 2 Item (10)

Thread 4 waiting for write lock Thread 3 waiting for read lock Thread 1 Item (20)

Thread 2 Item (20)

Thread 1 releasing read lock  // Поток 1 освобождает блокировку чтения Thread 2 releasing read lock

Thread 4 has write lock

Thread 4 releasing write lock Thread 3 has read lock  Thread 3 Item (10)

Thread 3 Item (20)

Thread 3 Item (30)

Thread 3 releasing read lock

Данный вывод отображает такую последовательность событий:

1. Поток 1 запрашивает и получает блокировку только для чтения.

2. Поток 1 выводит первое число в коллекции.

3. Поток 2 запрашивает и получает другую блокировку только для чтения.

4. Поток 2 выводит первое число в коллекции.

5. Поток 4 запрашивает блокировку для записи и вынужден ожидать ее.

6. Поток 3 запрашивает блокировку только для чтения, но т. к. поток 4 запросил блокировку для записи и ожидает в очереди, то поток 3 тоже ставится в очедь. На данном этапе потоки 3 и 4 стоят в очереди, ожидая освобождения блировок только для чтения потоков 1 и 2.

7. Потоки 1 и 2 выводят оставшиеся значения коллекции.

8. Потоки 1 и 2 освобождают блокировки только для чтения.

9. Поток 4 получает блокировку для записи, а поток 3 продолжает ожидать.

10.                       Поток 4 выполняет запись в коллекцию и освобождает блокировку для записи.

11.                       Поток 3  получает блокировку только для  чтения  и обрабатывает в цикле эленты коллекции, включая элемент, добавленный потоком 4.

Обратите внимание на то, как блокировка чтения/записи упорядочивает последательность событий чтения и записи, обеспечивая, таким образом, постоянство разделяемого состояния. Но блокировка чтения/записи не может предотвратить взаимоблокировок, которые могут возникнуть, если не соблюдать должную остожность при написании кода. Блокировка чтения/записи заботится только о коде, применяемом для управления данными.

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

  • Комментарии