48952 (597416), страница 2
Текст из файла (страница 2)
Из этих соображений следует наличие:
-
Критической секции или объекта исключительного владения для синхронизации потоков–писателей. Выбор одного из этих объектов определяется необходимостью исключительного доступа только одного потока–писателя к данным. Удобно также, что освободить секцию или mutex может только тот поток, который его занял. В рассматриваемом примере будем использовать объект mutex, для большей схожести с Рихтером (это позволит подчернуть несколько нюансов в разработке такого объекта).
-
События, переходящего в занятое состояние при наличии хотя–бы одного читателя. В данном случае целесообразно выбрать событие, которое будет сбрасываться в занятое состояние при появлении первого потока–читателя и устанавливаться в свободное последним читателем (последним из числа тех, кто пытается осуществить чтение одновременно с другими, но не вообще последнего).
-
Счетчика числа потоков–читателей, осуществляющих одновременный доступ. Нулевое значение счетчика соответствует установленному в свободное состояние событию. При увеличении счетчика проверяется его начальное значение, и если оно было 0, то событие сбрасывается в занятое состояние. При уменьшении счетчика проверяется результат — если он 0, то событие устанавливается в свободное состояние.
Можно примерно описать структуру такого объекта (назовем его NEWSWMRG, по сравнению с объектом SWMRG, рассматриваемым у Рихтера — Single Writer Multi Reader Guard). Эта структура должна быть описана примерно так:
struct NEWSWMRG {
СОБЫТИЕ НЕТ_ЧИТАТЕЛЕЙ;
СЧЕТЧИК ЧИСЛО_ЧИТАТЕЛЕЙ;
ОБЪЕКТ_ИСКЛЮЧИТЕЛЬНОГО_ВЛАДЕНИЯ ЕСТЬ_ПИСАТЕЛИ;
};
Для работы с ней надо будет выделить четыре специальных функции (инициализацию и удаление этого объекта пока не рассматриваем): две функции, используемые потоками–читателями для получения доступа к данным и для обозначения конца операции чтения, а также две аналогичных функции для потоков–писателей.
void RequestWriter( NEWSWMRG* p ) {
// дождаться разрешения для писателя
Захватить объект ЕСТЬ_ПИСАТЕЛИ;
// если объект получен во владение — других писателей больше нет
// и пока мы его не освободим — не появятся.
Дождаться события НЕТ_ЧИТАТЕЛЕЙ;
// если событие установлено — читателей также нет
}
void ReleaseWriter( NEWSWMRG* p ) {
// после изменения данных разрешаем доступ другим писателям и читателям
Освободить объект ЕСТЬ_ПИСАТЕЛИ;
}
void RequestReader( NEWSWMRG* p ) {
// дождаться разрешения для читателя — убедиться, что нет писателей
// для этого можно захватить объект ЕСТЬ_ПИСАТЕЛИ и сразу освободить его
// захват пройдет только тогда, когда писателей нет.
Захватить объект ЕСТЬ_ПИСАТЕЛИ;
// реально надо не только убедиться в отсутствии писателей, но и
// увеличить счетчик и при необходимости сбросить событие НЕТ_ЧИТАТЕЛЕЙ
if ( ЧИСЛО_ЧИТАТЕЛЕЙ == 0 ) Сбросить событие НЕТ_ЧИТАТЕЛЕЙ в занятое;
ЧИСЛО_ЧИТАТЕЛЕЙ++;
// а вот теперь можно смело освобождать объект ЕСТЬ_ПИСАТЕЛИ — во время
// работы читателя достаточно иметь сброшенное событие НЕТ_ЧИТАТЕЛЕЙ Освободить объект ЕСТЬ_ПИСАТЕЛИ;
}
void ReleaseReader( NEWSWMRG* p ) {
// после считывания данных уменьшаем счетчик и разрешаем доступ писателям
// при достижении нулевого значения счетчика.
--ЧИСЛО_ЧИТАТЕЛЕЙ;
if ( ЧИСЛО_ЧИТАТЕЛЕЙ == 0 ) Установить событие НЕТ_ЧИТАТЕЛЕЙ в свободное;
}
Естественно, это еще не рабочая схема, а только намек на нее. Полностью обсудим некоторые особенности при рассмотрении функций Win32 API. На первый взгляд виден небольшой “прокол” — в функции ReleaseReader счетчик сначала уменьшается, а только потом происходит проверка его значения и установка события при необходимости. Однако возможен (хотя и очень маловероятен) случай, когда поток будет прерван для выполнения операций другими потоками где–то между уменьшением счетчика и установкой события. В это время другие потоки могут изменить значение счетчика и событие будет установлено тогда, когда этого делать уже не следует.
Выйти из этой ситуации можно разными путями — либо добавлением еще одного объекта исключительного владения или критической секции для упорядочивания операций со счетчиком, либо другими способами. Для разбирательства с этими альтернативными способами следует рассмотреть синхронизацию с группой объектов.
1 Возможны такие объекты, хотя это не типичный случай, когда читатели не конфликтуют с писателями. Тогда в ситуациях 2 и 3 следует разрешать доступ.
2 В Win32 API существуют специальные механизмы, позволяющие возобновлять исполнение только одного потока при освобожении события. Однако, по сравнению с другими операционными системами, скажем OS/2, такое поведение события нетипично.















