Common Intermediate Language и системное программирование в Microsoft.Net. Макаров А.В - 141 стр.

UptoLike

private int m_a = 0, m_b = 0;
public int summ() {
int r;
m_rwlock.AcquireReaderLock( -1 );
try {
r = m_a; Thread.Sleep( 1000 ); return r + m_b;
} finally {
m_rwlock.ReleaseReaderLock();
}
}
public int inc() {
m_rwlock.AcquireWriterLock( -1 );
try {
m_a++; Thread.Sleep( 500 ); m_b++;
return m_a + m_b;
} finally {
m_rwlock.ReleaseWriterLock();
}
}
public static void Invoke( SomeData sd, int no ) {
if ( no % 2 == 0 ) {
Console.WriteLine( sd.inc() );
} else {
Console.WriteLine( sd.summ() );
}
}
}
public delegate void AsyncProcCallback(SomeData sd, int no);
class TestApp {
public static void Main() {
int i;
SomeData sd = new SomeData();
WaitHandle[] wh;
AsyncProcCallback apd;
wh = new WaitHandle[ SomeData.m_queries ];
apd = new AsyncProcCallback( SomeData.Invoke );
for ( i = 0; i < SomeData.m_queries; i++ ) wh[i] =
apd.BeginInvoke(sd,i,null,null).AsyncWaitHandle;
WaitHandle.WaitAll( wh );
}
}
}
Разработка параллельных приложений для ОС Windows
269
Приведенный пример показывает синхронизацию с использованием
мьютекса, события с ручным сбросом и объекта WaitHandle, представляюще-
го состояние асинхронного вызова. В примере делается 10 асинхронных вы-
зовов, после чего приложение ожидает завершения всех вызовов с помощью
метода WaitAll. Каждый асинхронный метод в секции кода, защищаемой
мьютексом (здесь было бы эффективнее использовать монитор или блоки-
ровку), подсчитывает число сделанных вызовов и переходит к ожиданию за-
нятого события. Самый последний асинхронный вызов установит событие в
свободное состояние, после чего все вызовы должны завершиться.
Помимо использования разных синхронизирующих объектов, в этом
примере интересно поведение CLR: асинхронные вызовы должны обраба-
тываться в пуле потоков, однако число вызовов превышает число потоков
в пуле. CLR по мере необходимости добавляет в пул потоки для обработ-
ки поступающих запросов.
Потоки не являются наследниками класса WaitHandle в силу того, что
для разных базовых платформ потоки могут быть реализованы в качестве
потоков операционной системы или легковесных потоков, управляемых
CLR. В последнем случае потоки .NET не будут иметь никаких аналогов
среди объектов ядра операционной системы. Для синхронизации с пото-
ками надо использовать метод Join класса Thread.
Один «писатель», много «читателей»
Одной из типичных задач синхронизации потоков является задача, в
которой допускается одновременный конкурентный доступ многих объе-
ктов для чтения данных («читатели») и исключительный доступ единст-
венного потока, вносящего в объект изменения («писатель»). В Win32 API
стандартного объекта, реализующего подобную логику, не существует, по-
этому каждый раз его надо проектировать и создавать заново.
.NET предоставляет весьма эффективное стандартное решение:
класс ReaderWriterLock. В приводимом ниже примере демонстрируется
применение методов Acquire... и Release... для корректного использова-
ния блокировки доступа при чтении и записи. Тестовый класс содержит
две целочисленные переменные, которые считываются и увеличиваются
на 1 с небольшими задержками по отношению друг к другу. Пока опера-
ции синхронизируются, попытка чтения или изменения всегда будет воз-
вращать четный результат, а вот если бы синхронизация не выполнялась,
то в некоторых случаях получались бы нечетные числа:
using System;
using System.Threading;
namespace TestNamespace {
public class SomeData {
public const int m_queries = 10;
private ReaderWriterLock m_rwlock = new ReaderWriterLock();
268
CIL и системное программирование в Microsoft .NET
268                        CIL и системное программирование в Microsoft .NET   Разработка параллельных приложений для ОС Windows                       269


     Приведенный пример показывает синхронизацию с использованием                        private int          m_a = 0, m_b = 0;
мьютекса, события с ручным сбросом и объекта WaitHandle, представляюще-                  public int summ() {
го состояние асинхронного вызова. В примере делается 10 асинхронных вы-                     int r;
зовов, после чего приложение ожидает завершения всех вызовов с помощью                      m_rwlock.AcquireReaderLock( -1 );
метода WaitAll. Каждый асинхронный метод в секции кода, защищаемой                          try {
мьютексом (здесь было бы эффективнее использовать монитор или блоки-                          r = m_a; Thread.Sleep( 1000 ); return r + m_b;
ровку), подсчитывает число сделанных вызовов и переходит к ожиданию за-                     } finally {
нятого события. Самый последний асинхронный вызов установит событие в                         m_rwlock.ReleaseReaderLock();
свободное состояние, после чего все вызовы должны завершиться.                              }
     Помимо использования разных синхронизирующих объектов, в этом                       }
примере интересно поведение CLR: асинхронные вызовы должны обраба-                       public int inc() {
тываться в пуле потоков, однако число вызовов превышает число потоков                       m_rwlock.AcquireWriterLock( -1 );
в пуле. CLR по мере необходимости добавляет в пул потоки для обработ-                       try {
ки поступающих запросов.                                                                      m_a++; Thread.Sleep( 500 ); m_b++;
     Потоки не являются наследниками класса WaitHandle в силу того, что                       return m_a + m_b;
для разных базовых платформ потоки могут быть реализованы в качестве                        } finally {
потоков операционной системы или легковесных потоков, управляемых                             m_rwlock.ReleaseWriterLock();
CLR. В последнем случае потоки .NET не будут иметь никаких аналогов                         }
среди объектов ядра операционной системы. Для синхронизации с пото-                      }
ками надо использовать метод Join класса Thread.                                         public static void Invoke( SomeData sd, int no ) {
     Один «писатель», много «читателей»                                                     if ( no % 2 == 0 ) {
     Одной из типичных задач синхронизации потоков является задача, в                         Console.WriteLine( sd.inc() );
которой допускается одновременный конкурентный доступ многих объе-                          } else {
ктов для чтения данных («читатели») и исключительный доступ единст-                           Console.WriteLine( sd.summ() );
венного потока, вносящего в объект изменения («писатель»). В Win32 API                      }
стандартного объекта, реализующего подобную логику, не существует, по-                   }
этому каждый раз его надо проектировать и создавать заново.                             }
     .NET предоставляет весьма эффективное стандартное решение:                         public delegate void AsyncProcCallback(SomeData sd, int no);
класс ReaderWriterLock. В приводимом ниже примере демонстрируется                       class TestApp {
применение методов Acquire... и Release... для корректного использова-                    public static void Main() {
ния блокировки доступа при чтении и записи. Тестовый класс содержит                         int                 i;
две целочисленные переменные, которые считываются и увеличиваются                           SomeData            sd = new SomeData();
на 1 с небольшими задержками по отношению друг к другу. Пока опера-                         WaitHandle[]        wh;
ции синхронизируются, попытка чтения или изменения всегда будет воз-                        AsyncProcCallback apd;
вращать четный результат, а вот если бы синхронизация не выполнялась,                       wh = new WaitHandle[ SomeData.m_queries ];
то в некоторых случаях получались бы нечетные числа:                                        apd = new AsyncProcCallback( SomeData.Invoke );
     using System;                                                                          for ( i = 0; i < SomeData.m_queries; i++ ) wh[i] =
     using System.Threading;                                                                    apd.BeginInvoke(sd,i,null,null).AsyncWaitHandle;
     namespace TestNamespace {                                                              WaitHandle.WaitAll( wh );
        public class SomeData {                                                           }
          public const int           m_queries = 10;                                    }
          private ReaderWriterLock m_rwlock = new ReaderWriterLock();               }