Читаем без скачивания Системное программирование в среде Windows - Джонсон Харт
Шрифт:
Интервал:
Закладка:
• Целевой поток переходит в состояние дежурного ожидания (alertable wait state), обеспечивающее возможность безопасного выполнения потоком АРС. Порядок первых двух шагов безразличен, поэтому о возникновении условий состязательности можно не беспокоиться.
• Указанный поток, находящийся в состоянии ожидания, выполняет все АРС, находящиеся в очереди.
• АРС могут выполнять любые нужные действия, например, освобождать ресурсы или генерировать исключения. Благодаря этому главный поток может инициировать возбуждение исключения в целевом потоке, хотя само исключение не произойдет до тех пор, пока целевой поток не перейдет в состояние дежурного ожидания.
Выполнение АРС является асинхронным в том смысле, что АРС может быть помещен в очередь целевого потока в любой момент, но само выполнение является синхронизированным в том смысле, что это может произойти лишь тогда, когда целевой поток входит в состояние дежурного ожидания.
Состояния дежурного ожидания будут вновь обсуждаться в главе 14, посвященной асинхронному вводу/выводу.
Описания необходимых функций и примеры их использования в другом варианте программы ThreeStage приводятся в следующих разделах. Проект для построения новой версии программы (ThreeStageCancel) и соответствующий исходный код (ThreeStageCancel. с) находятся на Web-сайте книги.
Очередизация асинхронных вызовов процедур
Один поток (главный) помещает АРС в очередь целевого потока с помощью функции QueueUserAPC:
DWORD QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, DWORD dwData)
hThread — дескриптор целевого потока. dwData — аргумент, который будет передан функции АРС при ее выполнении и может являться кодом завершения или сообщать функции иную информацию.
В основной функции программы ThreeStageCancel.с (сравните с программой 10.5) вызовы TerminateThread заменяются вызовами QueueUserAPC следующим образом:
// TerminateThread(transmitter_th, 0) ; заменить на АРС
// TerminateThread(receiver_th, 0); заменить на АРС
tstatus = QueueUserAPC(ShutDownTransmitter, transmitter_th, 1);
if (tstatus == 0) ReportError(…);
tstatus = QueueUserAPC(ShutDownReceiver, receiver_th, 2);
if (tstatus == 0) ReportError (…);
Функция QueueUserAPC в случае успешного ее завершения возвращает ненулевое значение, иначе — нуль. В то же время, функция GetLastError () не возвращает никакого полезного значения, и поэтому при вызове функции ReportError не требуется задавать текст сообщения об ошибке (значением последнего аргумента является FALSE).
pfnAPC — указатель на фактическую функцию, вызываемую целевым потоком, как показывает следующий фрагмент, взятый из программы ThreeStageCancel. с:
/* АРС для завершения выполнения потребителя. */
void WINAPI ShutDownReceiver(DWORD n) {
printf("Внутри ShutDownReceiver. %dn", n);
/* Освободить все ресурсы (в данном примере отсутствуют). */
return;
}
Функция ShutDownTransmitter аналогична вышеприведенной, отличаясь от нее только текстом сообщения. Сразу трудно понять, каким образом эта функция, которая, казалось бы, не выполняет никаких существенных операций, может инициировать прекращение выполнения целевого принимающего потока. Соответствующие пояснения приводятся далее в этой главе.
АРС и упущенные сигналы
АРС, выполняемые в режиме ядра (используются в операциях асинхронного ввода/вывода), могут немедленно выводить ожидающий поток из состояния ожидания, что может стать причиной потери сигналов PulseEvent. В связи с этим в документации можно встретить советы, в которых функции PulseEvent рекомендуется не использовать, хотя, как было продемонстрировано в данной главе, они могут и приносить пользу. Применение функции PulseEvent в наших примерах было вполне безопасным, поскольку АРС, выполняемые в режиме ядра, в них не используются. Кроме того, применение функции SignalObjectAndWait и тестирование возвращаемого ею значения обеспечивает достаточно надежную защиту от подобных потерь сигналов. Наконец, если вы опасаетесь, что это все-таки может случиться, просто включайте указание конечного интервала ожидания в соответствующие вызовы функций ожидания.
Состояния дежурного ожидания
Во всех предыдущих примерах значение параметра bAlertable, являющегося последним параметром функции SignalObjectAndWait, полагалось равным FALSE. Используя вместо него значение TRUE, мы указываем, что ожидание должно быть, как говорят, дежурным (alertable), и тогда после выполнения функции поток перейдет в состояние дежурного ожидания. В этом состоянии поток ведет себя следующим образом:
• Если один или более АРС помещаются в очередь потока (указанного в качестве целевого при вызове функции QueueUserAPC) еще до того, как либо объект, указываемый дескриптором hObjectToWaitOn (обычно таким объектом является событие), перейдет в сигнальное состояние, либо истечет интервал ожидания, то все эти потоки выполнятся (при этом не гарантируется какой-то определенный порядок их выполнения), а функция SignalObjectAndWait завершит выполнение, возвращая значение WAIT_IO_COMPLETED.
• Если АРС в очередь не помещались, то функция SignalObjectAndWait ведет себя обычным образом, то есть ожидает перехода объекта в сигнальное состояние или истечения интервала ожидания.
Состояния дежурного ожидания будут вновь использоваться нами при выполнении операций асинхронного ввода/вывода (глава 14); именно в связи с этими операциями и получило свое название значение WAIT_IO_COMPLETED. В состояние дежурного ожидания потока можно переводить также с помощью функций WaitForSingleObjectEx, WaitForMultipleObjectsEx и SleepEx, которые оказываются полезными и при выполнении операций асинхронного ввода/вывода.
Теперь можно изменить функции q_get и q_put (см. программу 10.4) таким образом, чтобы завершение работы программы после выполнения АРС было корректным, хотя АРС-функция и не выполняет никаких иных действий, кроме вывода сообщения и возврата из функции. Все, что в данном случае требуется — это организовать вход в состояние дежурного ожидания и проверить значение, возвращаемое функцией SignalObjectAndWait, как показано в приведенной ниже видоизмененной версии функции q_get (см. файл QueueObjCancel.с, находящийся на Web-сайте).
Программа 10.6. Модифицированная функция q_get, обеспечивающая корректное завершение выполнения потоковDWORD q_put(queue_t *q, PVOID msg, DWORD msize, DWORD MaxWait) {
BOOL Cancelled = FALSE;
if (q_destroyed(q)) return 1;
WaitForSingleObject(q->q_guard, INFINITE);
while (q_full(q) && !Cancelled) {
if (SignalObjectAndWait(q->q_guard, q->q_nf, INFINITE, TRUE) == WAIT_IO_COMPLETION) {
Cancelled = TRUE;
continue;
}
WaitForSingleObject(q->q_guard, INFINITE);
}
/* Поместить сообщение в очередь. */
if (!Cancelled) {
q_remove(q, msg, msize);
/* Сигнализировать о том, что очередь не заполнена, поскольку мы удалили сообщение. */
PulseEvent(q->q_nf);
ReleaseMutex(q->q_guard);
}
return Cancelled ? WAIT_TIMEOUT : 0;
}
В качестве функции АРС могут выступать и функция ShutDownReceiver, и функция ShutDownTransmitter, поскольку приемник и передатчик используют как функцию q_get, так и функцию q_put. Если требуется, чтобы функциям завершения было известно, из какого потока они выполняются, применяйте различные значения для аргументов функций АРС, которые передаются третьим аргументом функции QueueUserAPC во фрагменте кода, предшествующем программе 10.6.
Чтобы обеспечить согласованность с предыдущими версиями программы, в качестве кода завершения следует использовать значение WAIT_TIMEOUT.
В качестве альтернативного варианта вместо проверки совпадения возвращаемого значения со значением WAIT_IO_COMPLETION можно предусмотреть генерацию исключения функциями завершения и поместить тело функции q_put в try-блок, дополнив программу обработчиком исключений.
Безопасная отмена выполнения потоков
Обсуждение предыдущего примера продемонстрировало, как безопасно отменить выполнение целевого потока, который использует состояния дежурного ожидания. Несмотря на использование АРС, такую отмену выполнения иногда называют синхронной отменой (synchronous cancellation), поскольку отмена выполнения, которую инициировал вызов функции QueueUserAPC главным потоком, сможет осуществиться лишь тогда, когда целевой поток достигнет безопасного состояния дежурного ожидания.
Синхронная отмена выполнения требует участия в этом целевого потока, которая время от времени должна предоставлять возможность прекратить ее выполнение. Естественный способ вхождения в состояние дежурного ожидания предоставляют функции ожидания событий, поскольку в процессе прекращения работы системы объект события может не перейти вновь в сигнальное состояние. Ожидание мьютексов также можно выполнять в дежурном режиме, чтобы учесть те случаи, когда поток ожидает ресурса, который, возможно, не будет вновь доступным. Этот метод, например, может использоваться главным потоком для разрушения взаимной блокировки потоков.