Плюсы и минусы оповестительного ввода-вывода
октября 12 2009 by admin in Обязательный материалОбсудив механизмы выполнения оповестительного ввода-вывода, рассмотрим два вопроса, которые помогут вам понять, почему оповестительный ввод-вывод не годится для реализации ввода-вывода.
Функции обратного вызова Оповестительный ввод-вывод требует, чтобы вы создавали функции обратного вызова, что значительно усложняет реализацию вашего кода. У этих функций обычно нет достаточной текущей информации о конкретной задаче, решаемой в данный момент, поэтому приходится хранить множество данных в глобальных переменных. К счастью, эти глобальные переменные не нужно синхронизировать, так как функции обратного вызова выполняются в том лее потоке, что обращается к одной из пяти функций, переводящих поток в состояние ожидания оповещения. Поскольку поток не может одновременно выполняться в двух точках, эти переменные не повреждаются. Работа с несколькими потоками Действительно серьезная проблема связана с тем, что обработка уведомления о завершении должна осуществляться в том же потоке, что выдал запрос ввода-вывода. Если он выдал несколько запросов, то сам должен обработать уведомления о завершении каждого из них, даже если другие потоки п это время бездействуют. Из-за отсутствия распределения нагрузки приложение плохо масштабируется.
Обе проблемы достаточно серьезны, и я вам настоятельно рекомендую избегать использования этого метода для реализации ввода-вывода. Теперь вы наверняка догадались, что обе проблемы позволяет решить механизм порта завершения ввода-вывода, описываемый в следующем разделе. Но я обещал рассказать о положительных моментах, связанных с инфраструктурой уведомительного ввода-вывода, так что, прежде чем перейти к порту завершения ввода-вывода, поговорим о них.
В Windows есть функция, позволяющая вручную занести запись в АРС-оче-редь потока:
DWORD QueueUserAPC( PAPCFUNC pfnAPC, HANDLE hThread, UL0NG_PTR dwData);
Первый параметр — это указатель на функцию асинхронного вызова, которая должна иметь такой прототип:
VOID WINAPI APCFunc(ULONG_PTR dwParam);
Второй параметр — описатель потока, для которого вы ставите запись в очередь. Это может быть любой поток в системе. Если параметр hThread определяет поток в адресном пространстве другого процесса, pfnAPC должен определять адрес памяти функции, находящейся в адресном пространстве того же другого процесса. Параметр dwData — просто передаваемое функции обратного вызова значение.
В описании функции QueueUserAPC указывается, что она возвращает значение типа DWORD, но на самом деле она возвращает значение типа BOOL, указывающее, успешно ли завершилась функция. QueueUscrj\PC можно применять для исключительно эффективного взаимодействия между потоками, даже содержащимися в разных процессах. Жалко, правда, что ей можно передавать лишь одно значение.
QueueUser/iPC можно использовать и для вывода процесса из состояния ожидания. Допустим, у вас есть поток, обратившийся к WaitForSingleOhject для ожидания сигнала от того или иного объекта ядра, и пользователь хочет завершить работу приложения во время этого ожидания. Вы знаете, что поток может корректно себя уничтожить, но как его разблокировать, если он в состоянии ожидания? С помощью функции QueueUserAPC.
Приведенный ниже код демонстрирует, как вывести поток из состояния ожидания, чтобы он корректно прекратил работу. Основная функция порождает новый поток, передавая ему описатель некоторого объекта ядра. Во время выполнения вторичного потока основной поток также выполняется. Вторичный поток (выполняющий функцию ThreadFunc) обращается к WaitFor-SingleObjectEx, которая приостанавливает поток, переводя его в состояние ожидания оповещения. Пусть теперь пользователь просит основной поток прервать приложение. Естественно, основной поток просто прервется, и система уничтожит процесс целиком, но такой подход не очень аккуратен, и, кроме того, во многих случаях, вам нужно просто отменить какую-то операцию, а не завершать весь процесс.
Поэтому основной поток вызывает QueueUserAPC, которая заносит запись в АРС-очередь вторичного потока. Поскольку вторичный поток находится в состоянии ожидания оповещения, он при этом будет разблокирован и очистит АРС-очередь, вызвав функцию APCFunc. Эта функция только возвращает управление. Поскольку теперь АРС-очередь пуста, в потоке производится выход из функции WaitForSingleObjectEx с кодом возврата WAIT_IO_COMPLETION. Функция ThreadFunc проверяет именно это значение, зная, что в этом случае надо прекратить работу потока.