Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
375
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

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

Каждый вторичный поток выводит на экран такое сообщение:

Чтобы данный поток имитировал чтение ресурса, щелкните кнопку Yes, a чтобы оптимитировал запись в ресурс — кнопку No. Эти действия просто заставляют его вызвать либо функцию WaitToRead, либо функцию WaitToWrite объекта CSWMRG.

После вызова одной из этих функций поток выводит соответствующее сообщение.

Пока окно с сообщением открыто, программа приостанавливает поток и делает вид, будто он сейчас работает с ресурсом

Конечно, если какой-то поток читает данные из ресурса и Вы командуете другому потоку записать данные в ресурс, окно с сообщением от последнего на экране не появится, так как поток-«писатель» ждет освобождения ресурса, вызвав WaitToWrite. Аналогичным образом, если Вы скомандуете потоку считать данные из ресурса в то время, как показывается окно с сообщением от потока-«писателя», первый поток бу дет ждать в вызове WaitToRead, и его окно не появится до тех пор, пока все потоки «писатели» не закончат имитировать свою работу с ресурсом

Закрыв окно с сообщением (щелчком кнопки OK), Вы заставите поток, получив ший доступ к ресурсу, вызвать Done, и объект CSWMRG переключится на другие жду щие потоки.

Реализация функции WaitForMultipleExpressions

Некогорое время назад я разрабатывал одно приложение и столкнулся с весьма не простым случаем синхронизации потоков Функции WaitForMultipleObjects., заставля ющей поток ждать освобождения одного или всех объектов, оказалось недостаточно Мне понадобилась функция, которая позволяла бы задавать более сложные критерии ожидания У меня было три объекта ядра процесс, семафор и событие Мой поток должен был ждать до тех пор, пока не освободтся либо процесс и семафор, либо процесс и событие

Слегка поразмыслив и творчески использовав имеющиеся функции Windows, я создал именно то, что мне требовалось, — функцию WaitWorMulttpleExpressions Ее прототип выглядит так

DWORD WINAPI WaitForMultipleExpressions( DWORD nExpObjectS, CONST HANDLE* phExpObiects, DWORD dwMilliseconds);

Перед ее вызовом Вы должны создать массив описателей (HANDLE) и инициали зировать всс cro элементы. Параметр nExpObjects сообщает число элементов в масси ве, на который указывает параметр phExpObjects. Этот массив содержит несколько наборов описателей объектов ядра; при этом каждый набор отделяется элементом, равным NULL. Функция WaitForMultipleExpressions считает все объекты в одном набо ре объединяемыми логической операцией AND, а сами наборы — объединяемыми логической операцией OR. Поэтому WaitForMultipleExpressions приостанавливает вызы вающий поток до тех пор, пока нс освободятся сразу все объекты в одном из наборов.

Вот пример. Допустим, мы работаем с четырьмя объектами ядра (см. таблицу ниже).

Обьект ядра

Значение описателя

 

 

Поток

0x1111

 

 

Семафор

0x2222

 

 

Событие

0x3333

 

 

Процесс

0x4444

 

 

Инициализировав массив описателей, как показано в следующей таблице, мы со общаем функции WaitForMultipleExpressions приостановить вызывающий поток до тех пор, пока не освободятся поток AND семафор OR семафор AND событие AND про цесс OR поток AND процесс,

Индекс

Значение описателя

Набор

 

 

 

 

0

0x1111

(поток)

0

 

 

 

 

1

0x2222

(семафор)

 

 

 

 

 

2

0x0000

(OR)

 

 

 

 

 

3

0x2222

(семафор)

1

 

 

 

 

4

0x3333

(событие)

 

 

 

 

 

5

0x4444

(процесс)

 

 

 

 

 

6

0x0000

(OR)

 

 

 

 

 

 

7

0x1 1 1

1 (поток)

2

 

 

 

 

8

0x4444

(процесс)

 

 

 

 

 

Вы, наверное, помните, что функции WaitForMultipleObjects нельзя передать массив описателей, число элементов в котором превышает 64 (MAXIMUM_WAIT_OBJECTS). Так вот, при использовании WaitForMultipleExpressions массив описателей может быть го раздо больше. Однако у Вас не должно быть более 64 выражений, а в каждом — более 63 описателей. Кроме того, WaitForMulttpleExpresstons будет работать некорректно, если Вы передадите ей хотя бы один описатель мыотекса. (Почему — объясню позже.)

Возвращаемые значения функции WaitForMultipleExpressions показаны в следующей таблице. Если заданное выражение становится истинным, WaitForMultipleExpressions

возвращает индекс этого выражения относительно WAIT_OBJECT_0. Если взять тот же пример, то при освобождении объектов «поток» и «процесс» WaitforMultipleExpressions вернет индекс в виде WAIT_OBJECT_0 + 2.

Возвращаемое

Описание

значение

 

От WAIT_OBJECT_0

Указывает, какое выражение стало истинным

до (WAIT_OBJECT_0

 

+ число выражений -

 

1)

 

WAIT_TIMEOUT

Ни одно выражение не стало истинным в течение заданного

 

времени.

 

 

WAIT_FAILED

Произошла ошибка. Чтобы получить более подробную инфор

 

мацию, вызовите GetLastError. Код

 

ERROR_TOO_MANY_SECRETS означает, что Вы указали более

 

61 выражений, a ERROR_SEC RET_ТОО_LONG — что по крайней

 

мере в одном выражении указано более 63 объектов. Могут

 

возвращаться коды и других ошибок

 

 

Программа-пример WaitForMultExp

Эта программа, «10 WaitForMultExp.exe» (см. листинг на рис. 10-4), предназначена для тестирования функции WaitForMultipleExpressions Файлы исходного кода и ресурсов этой программы находятся в каталоге l0-WaitForMultExp на компакт-диске, прилага емом к кпиге. После запуска WaitForMultExp открывается диалоговое окно, показан ное ниже.

Если Вы нс станете изменять предлагаемые параметры, а просто щелкнете кноп ку Wait For Multiple Expressions, диалоговое окно будет выглядеть так, как показано на следующей иллюстрации.

Программа создаст четыре объекта ядра «событие» в занятом состоянии и поме щает в многоколоночный список (с возможностью выбора сразу нескольких элемен тов) по одной записи для каждого объекта ядра. Далее программа анализирует содер жимое поля Expression и формирует массив описателей. По умолчанию я предлагаю объекты ядра и выражение, как в предыдущем примере.

Поскольку я задал время ожидания равным 30000 мс, у Вас есть 30 секунд на вне сение изменений. Выбор элемента в нижнем списке приводит к вызову SetEvent, ко

торая освобождает объект, а отказ от его выбора — к вызову ResetEvent и соответствен но к переводу объекта в занятое состояние. После выбора достаточного числа эле ментов (удовлетворяющего одному из выражений) WaitForMultipleExpressions возвра щает управление, и в нижней части диалогового окна показывается, какому выраже нию удовлетворяет Ваш выбор. Если Вы не уложитесь в 30 секунд, появится слово "Timeout"

Теперь обсудим мою функцию WaitForMultipleExpressions. Реализовать ее было не просто, и ее применение, конечно, приводит к некоторым издержкам. Как Вы знаете, в Windows есть функция WaitForMultipleOtyects, которая позволяет потоку ждать по единственному AND-выражснию.

DWORD WaitForMultipleObjects( DWORD dwObjects, CONST HANDLE* pliObjects, BOOL fWaitAll, DWORD dwMimseconds);

Чтобы расширить ее функциональность для поддержки выражений, объединяемых OR, я должен создать несколько потоков — по одному на каждое такое выражение Каждый из этих потоков ждет в вызове WaitForMultipleObjectsEx по единственному AND-выражснию (Почему я использую эту функцию вместо более распространенной WaitForMultipleObjects - станет ясно позже) Когда какое-то выражение становится истинным, один из созданных потоков пробуждается и завершается

Поток, который вызвал WaitForMultipleExpressions (и который породил все OR-пo токи), должен ждать, пока одно из OR-выражений пе станет истинным. Для этого он вызывает функцию WaitForMultipleQbjeclsEx. В параметре dwObjects передается коли чество порожденных потоков (OR-выражений), а параметр phObjects указывает на массив

описателей этих потоков. В паряметр fWaitAll записывается FALSE, чтобы ос новной поток пробудился сразу после того, как оанет истинным любое из выраже ний. И, наконец, в параметре dwMilliseconds передается значение, идентичное тому, которое было указано в аналогичном параметре при вызове WaitForMultipleExpressions

Если в течение заданного времени ни одно из выражений не становится истин ным, WaitForMultipleObjectsEx возвращает WAIT_TIMHOUT, и это же значение возвpa щается функцией WaitForMiltipleExpressions А если какое-нибудь выражение становит

ся истинным, WaitForMultipleObjectsEx возвращает индекс, указывающий, какой поток завершился Так как каждый поток представляет отдельное выражение, этот индекс сообщает и то, какое выражение стало истинным; этот же индекс возвращается и функцией WaitForMultipleExpressions.

На этом мы, пожалуй, закончим рассмотрение того, как работает функция WaiWor MultipleExpressions. Но нужно обсудить еще три вещи. Во-первых, нельзя допустить, чтобы несколько OR-потоков одновременно пробудились в своих вызовах WaitFor MultipleObjectsEx, так как успешное ожидание некоторых объектов ядра приводит к изменению их состояния (например, у семафора счетчик уменьшается на 1) WaitFor MultipleExpressions ждет лишь до тех пор, пока одно из выражений не станет истин ным, а значит, я должен предотвратить более чем однократное изменение состояния объекта

Решить эту проблему на самом деле довольно легко. Прежде чем порождать OR потоки, я создаю собственный объект-семафор с начальным значением счетчика, равным 1 Далее каждый OR-поток вызывает WaitForMultipleObjectsEx и передает ей не только описатели объектов, связанных с выражением, но и описатель этого семафо ра. Теперь Вы понимаете, почему в каждом наборе не может быть более 63 описате лей? Чтобы ORпоток пробудился, должны освободиться все объекты, которые он ждет, — в том числе мой специальный семафор. Поскольку начальное значение его счетчика равно 1, более одного OR-потока никогда не пробудится, и, следовательно, случайного изменения состояния каких-либо других объектов нс произойдет.

Второе, на что нужно обратить внимание, - как заставить ждущий поток прекра тить ожидание для корректной очистки. Добавление семафора гарантирует, что про будится не более чем один поток, но, раз мне уже известно, какое выражение стало истинным, я должен пробудить и остальные потоки, чтобы они корректно заверши лись. Вызова TerminateThread следует избегать, поэтому нужен какой-тодругой меха низм. Поразмыслив, я вспомнил, что потоки, ждущие в «тревожном" состоянии, при нудительно пробуждаются, когда в АРС-очереди появляется какой-нибудь элемент.

Моя реализация WaitForMultipleExpressions для принудительного пробуждения по токов использует QueueUserAPC. После того как WaitForMultipleObjects, вызванная ос новным потоком, возвращает управление, я ставлю АРС-вызов в соответствующие очереди каждого из все еще ждущих OR-потоков:

//выводим все еще ждущие потоки из состояния сна,

//чтобы они могли корректно завершиться

for (dwExpNum = 0; dwExpNum < dwNumExps; dwExpNum++)

{

if ((WAIT_TIMEOUT == dwWaitRet) || (dwExpNum != (dwWaitRet - WAIT_OBJECT_0)))

{

QueueUserAPC(WFME_ExpressionAPC, ahThreads[dwExpNum], 0);

}

}

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

// это АРС-функция обратного вызова

VOID WINAPI WFHE_ExpressionAPC(DWORD dwData}

{

// в тело функции преднамеренно не включено никаких операторов

}

Третье (и последнее) — правильная обработка интервалов ожидания. Если ника кие выражения так и не стали истинными в течение заданного времени, функция

WaitForMultipleObjects, вызванная основным потоком, возвращает WAIT_TIMEOUT В этом случае я должен позаботиться о том, чтобы ни одно выражение больше не ста ло бы истинным и тем самым не изменило бы состояние объектов. За это отвечает следующий код

// ждем, когда выражение станет TRUE или когда истечет срок ожидания

dwWaitRet = WaitForMultiplcObjects(dwExpNum, ahThreads, FALSE, dwMilliseconds);

if (WAIT_TIMEOUT == dwWaitRet)

{

//срок ожидания истек, выясняем, не стало ли какое-нибудь выражение

//истинным, проверяя состояние семафора hsemOnlyOne dwWaitRet = WaitForSingleObject(hsemOnlyOne, 0);

if (WAIT_TIMEOUT == dwWaitRet}

{

//если семафор не был переведен в свободное состояние,

//какое-то выражение дало TRUE, надо выяснить - какое dwWaitRet = WaitForMultipleObjects(dwExpNum, ahThreads, FALSE. INFINITE);

}

else

{

//ни одно выражение не стало TRUE,

//и WaitForSingleObject просто отдала нам семафор

dwWaitRet = WAIT_TIMbOUT;

}

}

Я не даю другим выражениям стать истинными за счет ожидания на семафоре. Это приводит к уменьшению счетчика семафора до 0, и никакой OR-поток не может про будиться. Но где-то после вызова функции WaitForMultipleObjects из основного пото ка и обращения той к WaitForSingleObject одно из выражений может стать истинным Вот почемуя проверяю значение, возвращаемое WaitForSingleQbject. Если она возвра щает