
- •WINDOWS
- •Джеффри Рихтер
- •ЧАCTЬ I МАТЕРИАЛЫ ДЛЯ ОБЯЗАТЕЛЬНОГО ЧТЕНИЯ
- •ГЛАВА 1. Обработка ошибок
- •Вы тоже можете это сделать
- •Программа-пример ErrorShow
- •ГЛАВА 2 Unicode
- •Наборы символов
- •Одно- и двухбайтовые наборы символов
- •Unicode: набор широких символов
- •Почему Unicode?
- •Windows 2000 и Unicode
- •Windows 98 и Unicode
- •Windows CE и Unicode
- •В чью пользу счет?
- •Unicode и СОМ
- •Как писать программу с использованием Unicode
- •Unicode и библиотека С
- •Типы данных, определенные в Windows для Unicode
- •Unicode- и ANSI-функции в Windows
- •Строковые функции Windows
- •Ресурсы
- •Текстовые файлы
- •Перекодировка строк из Unicode в ANSI и обратно
- •ГЛАВА 3 Объекты ядра
- •Что такое объект ядра
- •Учет пользователей объектов ядра
- •Защита
- •Таблица описателей объектов ядра
- •Создание объекта ядра
- •Закрытие объекта ядра
- •Совместное использование объектов ядра несколькими процессами
- •Наследование описателя объекта
- •Изменение флагов описателя
- •Именованные объекты
- •Пространства имен Terminal Server
- •Дублирование описателей объектов
- •ЧАСТЬ II НАЧИНАЕМ РАБОТАТЬ
- •ГЛАВА 4 Процессы
- •Ваше первое Windows-приложение
- •Описатель экземпляра процесса
- •Описатель предыдущего экземпляра процесса
- •Командная строка процесса
- •Переменные окружения
- •Привязка к процессорам
- •Режим обработки ошибок
- •Текущие диск и каталог для процесса
- •Текущие каталоги для процесса
- •Определение версии системы
- •Функция CreateProcess
- •Параметры pszApplicationName и pszCommandLine
- •Параметры psaProcess, psaThread и blnheritHandles
- •Параметр fdwCreate
- •Параметр pvEnvironment
- •Параметр pszCurDir
- •Параметр psiStartlnfo
- •Параметр ppiProclnfo
- •Завершение процесса
- •Возврат управления входной функцией первичного потока
- •Функция ExitProcess
- •Функция TerminateProcess
- •Когда все потоки процесса уходят
- •Что происходит при завершении процесса
- •Дочерние процессы
- •Запуск обособленных дочерних процессов
- •Перечисление процессов, выполняемых в системе
- •Программа-пример Processlnfo
- •ГЛАВА 5 Задания
- •Определение ограничений, налагаемых на процессы в задании
- •Включение процесса в задание
- •Завершение всех процессов в задании
- •Получение статистической информации о задании
- •Уведомления заданий
- •Программа-пример JobLab
- •ГЛАВА 6 Базовые сведения о потоках
- •В каких случаях потоки создаются
- •И в каких случаях потоки не создаются
- •Ваша первая функция потока
- •Функция CreateThread
- •Параметр psa
- •Параметр cbStack
- •Параметры pfnStartAddr и pvParam
- •Параметр fdwCreate
- •Параметр pdwThreadlD
- •Завершение потока
- •Возврат управления функцией потока
- •Функция ExitThread
- •Функция TerminateThread
- •Если завершается процесс
- •Что происходит при завершении потока
- •Кое-что о внутреннем устройстве потока
- •Некоторые соображения по библиотеке С/С++
- •Ой, вместо _beginthreadex я по ошибке вызвал CreateThread
- •Библиотечные функции, которые лучше не вызывать
- •Как узнать о себе
- •Преобразование псевдоописателя в настоящий описатель
- •ГЛАВА 7 Планирование потоков, приоритет и привязка к процессорам
- •Приостановка и возобновление потоков
- •Приостановка и возобновление процессов
- •Функция Sleep
- •Переключение потоков
- •Определение периодов выполнения потока
- •Структура CONTEXT
- •Приоритеты потоков
- •Абстрагирование приоритетов
- •Программирование приоритетов
- •Динамическое изменение уровня приоритета потока
- •Подстройка планировщика для активного процесса
- •Программа-пример Scheduling Lab
- •Привязка потоков к процессорам
- •ГЛАВА 8 Синхронизация потоков в пользовательском режиме
- •Кэш-линии
- •Более сложные методы синхронизации потоков
- •Худшее, что можно сделать
- •Критические секции
- •Критические секции: важное дополнение
- •Критические секции и спин-блокировка
- •Критические секции и обработка ошибок
- •Несколько полезных приемов
- •Не занимайте критические секции надолго
- •ГЛАВА 9 Синхронизация потоков с использованием объектов ядра
- •Wait-функции
- •Побочные эффекты успешного ожидания
- •События
- •Программа-пример Handshake
- •Ожидаемые таймеры
- •Ожидаемые таймеры и АРС-очередь
- •И еще кое-что о таймерах
- •Семафоры
- •Мьютексы
- •Отказ от объекта-мьютекса
- •Мьютексы и критические секции
- •Программа-пример Queue
- •Сводная таблица объектов, используемых для синхронизации потоков
- •Другие функции, применяемые в синхронизации потоков
- •Асинхронный ввод-вывод на устройствах
- •Функция WaitForlnputldle
- •Функция MsgWaitForMultipleObjects(Ex)
- •Функция WaitForDebugEvent
- •Функция SignalObjectAndWait
- •ГЛАВА 10 Полезные средства для синхронизации потоков
- •Реализация критической секции: объект-оптекс
- •Программа-пример Optex
- •Создание инверсных семафоров и типов данных, безопасных в многопоточной среде
- •Программа-пример lnterlockedType
- •Синхронизация в сценарии "один писатель/группа читателей"
- •Программа-пример SWMRG
- •Реализация функции WaitForMultipleExpressions
- •Программа-пример WaitForMultExp
- •ГЛАВА 11 Пулы потоков
- •Сценарий 1: асинхронный вызов функций
- •Сценарий 2: вызов функций через определенные интервалы времени
- •Программа-пример TimedMsgBox
- •Сценарий 3: вызов функций при освобождении отдельных объектов ядра
- •Сценарий 4; вызов функций по завершении запросов на асинхронный ввод-вывод
- •ГЛАВА 12 Волокна
- •Работа с волокнами
- •Программа-пример Counter
- •ЧАСТЬ III УПРАВЛЕНИЕ ПАМЯТЬЮ
- •Виртуальное адресное пространство процесса
- •Как адресное пространство разбивается на разделы
- •Увеличение раздела для кода и данных пользовательского режима до 3 Гб на процессорах x86 (только Windows 2000)
- •Закрытый раздел размером 64 Кб (только Windows 2000)
- •Раздел для общих MMF (только Windows 98)
- •Регионы в адресном пространстве
- •Передача региону физической памяти
- •Физическая память и страничный файл
- •Физическая память в страничном файле не хранится
- •Атрибуты защиты
- •Защита типа «копирование при записи»
- •Специальные флаги атрибутов защиты
- •Подводя итоги
- •Блоки внутри регионов
- •Особенности адресного пространства в Windows 98
- •Выравнивание данных
- •ГЛАВА 14 Исследование виртуальной памяти
- •Системная информация
- •Программа-пример Syslnfo
- •Статус виртуальной памяти
- •Программа-пример VMStat
- •Определение состояния адресного пространства
- •Функция VMQuery
- •Программа-пример VMMap
- •ГЛАВА 15 Использование виртуальной памяти в приложениях
- •Резервирование региона в адресном пространстве
- •Передача памяти зарезервированному региону
- •Резервирование региона с одновременной передачей физической памяти
- •В какой момент региону передают физическую память
- •Возврат физической памяти и освобождение региона
- •В какой момент физическую память возвращают системе
- •Программа-пример VMAIloc
- •Изменение атрибутов защиты
- •Сброс содержимого физической памяти
- •Программа-пример MemReset
- •Механизм Address Windowing Extensions (только Windows 2000)
- •Программа-пример AWE
- •ГЛАВА 16 Стек потока
- •Стек потока в Windows 98
- •Функция из библиотеки С/С++ для контроля стека
- •Программа-пример Summation
- •ГЛАВА 17 Проецируемые в память файлы
- •Проецирование в память EXE- и DLL-файлов
- •Статические данные не разделяются несколькими экземплярами EXE или DLL
- •Программа-пример Applnst
- •Файлы данных, проецируемые в память
- •Метод 1: один файл, один буфер
- •Метод 2: два файла, один буфер
- •Метод 3: один файл, два буфера
- •Метод 4: один файл и никаких буферов
- •Использование проецируемых в память файлов
- •Этап1: создание или открытие объекта ядра «файл»
- •Этап 2: создание объекта ядра «проекция файла»
- •Этап 3: проецирование файловых данных на адресное пространство процесса
- •Этап 4: отключение файла данных от адресного пространства процесса
- •Этапы 5 и 6: закрытие объектов «проекция файла» и «файл»
- •Программа-пример FileRev
- •Обработка больших файлов
- •Проецируемые файлы и когерентность
- •Базовый адрес файла, проецируемого в память
- •Особенности проецирования файлов на разных платформах
- •Совместный доступ процессов к данным через механизм проецирования
- •Файлы, проецируемые на физическую память из страничного файла
- •Программа-пример MMFShare
- •Частичная передача физической памяти проецируемым файлам
- •Программа-пример MMFSparse
- •ГЛАВА 18 Динамически распределяемая память
- •Стандартная куча процесса
- •Дополнительные кучи в процессе
- •Защита компонентов
- •Более эффективное управление памятью
- •Локальный доступ
- •Исключение издержек, связанных с синхронизацией потоков
- •Быстрое освобождение всей памяти в куче
- •Создание дополнительной кучи
- •Выделение блока памяти из кучи
- •Изменение размера блока
- •Определение размера блока
- •Освобождение блока
- •Уничтожение кучи
- •Использование куч в программах на С++
- •Другие функции управления кучами
- •ЧАСТЬ IV ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМЫЕ БИБЛИОТЕКИ
- •ГЛАВА 19 DLL: основы
- •DLL и адресное пространство процесса
- •Общая картина
- •Создание DLL-модуля
- •Что такое экспорт
- •Создание DLL для использования с другими средствами разработки (отличными от Visual C++)
- •Создание ЕХЕ-модуля
- •Что такое импорт
- •Выполнение ЕХЕ-модуля
- •ГЛАВА 20 DLL: более сложные методы программирования
- •Явная загрузка DLL и связывание идентификаторов
- •Явная загрузка DLL
- •Явная выгрузка DLL
- •Явное подключение экспортируемого идентификатора
- •Функция входа/выхода
- •Уведомление DLL_PROCESS_ATTACH
- •Уведомление DLL_PROCESS_DETACH
- •Уведомление DLL_THREAD_ATTACH
- •Уведомление DLL_THREAD_DETACH
- •Как система упорядочивает вызовы DIIMain
- •Функция DllMain и библиотека С/С++
- •Отложенная загрузка DLL
- •Программа-пример DelayLoadApp
- •Переадресация вызовов функций
- •Известные DLL
- •Перенаправление DLL
- •Модификация базовых адресов модулей
- •Связывание модулей
- •ГЛАВА 21 Локальная память потока
- •Динамическая локальная память потока
- •Использование динамической TLS
- •Статическая локальная память потока
- •Пример внедрения DLL
- •Внедрение DLL c использованием реестра
- •Внедрение DLL с помощью ловушек
- •Утилита для сохранения позиций элементов на рабочем столе
- •Внедрение DLL с помощью удаленных потоков
- •Программа-пример lnjLib
- •Библиотека lmgWalk.dll
- •Внедрение троянской DLL
- •Внедрение DLL как отладчика
- •Внедрение кода в среде Windows 98 через проецируемый в память файл
- •Внедрение кода через функцию CreateProcess
- •Перехват API-вызовов: пример
- •Перехват API-вызовов подменой кода
- •Перехват API-вызовов с использованием раздела импорта
- •Программа-пример LastMsgBoxlnfo
- •ЧАСТЬ V СТРУКТУРНАЯ ОБРАБОТКА ИСКЛЮЧЕНИЙ
- •ГЛАВА 23 Обработчики завершения
- •Примеры использования обработчиков завершения
- •Funcenstein1
- •Funcenstein2
- •Funcenstein3
- •Funcfurter1
- •Проверьте себя: FuncaDoodleDoo
- •Funcenstein4
- •Funcarama1
- •Funcarama2
- •Funcarama3
- •Funcarama4: последний рубеж
- •И еще о блоке finally
- •Funcfurter2
- •Программа-пример SEHTerm
- •ГЛАВА 24 Фильтры и обработчики исключений
- •Примеры использования фильтров и обработчиков исключений
- •Funcmeister1
- •Funcmeister2
- •EXCEPTION_EXECUTE_HANDLER
- •Некоторые полезные примеры
- •Глобальная раскрутка
- •Остановка глобальной раскрутки
- •EXCEPTION_CONTINUE_EXECUTION
- •Будьте осторожны с EXCEPTION_CONTINUE_EXECUTION
- •EXCEPTION_CONTINUE_SEARCH
- •Функция GetExceptionCode
- •Функция GetExceptionlnformation
- •Программные исключения
- •ГЛАВА 25 Необработанные исключения и исключения С++
- •Отладка по запросу
- •Отключение вывода сообщений об исключении
- •Принудительное завершение процесса
- •Создание оболочки вокруг функции потока
- •Создание оболочки вокруг всех функций потоков
- •Автоматический вызов отладчика
- •Явный вызов функции UnhandledExceptionFilter
- •Функция UnhandledExceptionFilter изнутри
- •Исключения и отладчик
- •Программа-пример Spreadsheet
- •Исключения С++ и структурные исключения
- •Перехват структурных исключений в С++
- •ЧАСТЬ VI ОПЕРАЦИИ С ОКНАМИ
- •ГЛАВА 26 Оконные сообщения
- •Очередь сообщений потока
- •Посылка асинхронных сообщений в очередь потока
- •Посылка синхронных сообщений окну
- •Пробуждение потока
- •Флаги состояния очереди
- •Алгоритм выборки сообщений из очереди потока
- •Пробуждение потока с использованием объектов ядра или флагов состояния очереди
- •Передача данных через сообщения
- •Программа-пример CopyData
- •ГЛАВА 27 Модель аппаратного ввода и локальное состояние ввода
- •Поток необработанного ввода
- •Локальное состояние ввода
- •Ввод с клавиатуры и фокус
- •Управление курсором мыши
- •Подключение к очередям виртуального ввода и переменным локального состояния ввода
- •Программа-пример LISLab
- •Программа-пример LISWatch

WAIT_OBJECT_0, значит, семафор захвачен основным потоком и ни одно из выражений не стало истинным. Но если она возвращает WAIT_TIMEOUT, какое-то выражение все же стало истинным, прежде чем основной поюк успел захватить се мафор. Чтобы выяснить, какое именно выражение дало TRUE, основной поток снова вызывает WaitForMultipleObjects, но уже с временем ожидания, равным INFINITE; здесь все в порядке, так как я знаю, что семафор захвачен OR-потоком и этот поток вот-вот завершится Теперь я должен пробудить остальные OR-потоки, чтобы корректно за вершить их Это делается в цикле, из которого вызывается QueueUserAPC (о ней я уже рассказывал).
Поскольку реализация WintForMultipleExpressions основана на использовании груп пы потоков, каждый из которых ждет на своем наборе объектов, объединяемых по AND, мьютексы в ней неприменимы. В отличие от остальных объектов ядра мьютек сы могут передаваться потоку во владение. Значит, если какой-нибудь из моих AND потоков заполучит мьютекс, то по его завершении произойдет отказ от мьютекса. Вот когда Microsoft добавит в Windows API функцию, позволяющую одному потоку пере давать права на владение мьютексом другому потоку, тогда моя функция WaitFor MultipleExpressions и сможет поддерживать мьютексы. А пока надежного и корректного способа ввести в WattForMultipleExpressions такую поддержку я не вижу.
ГЛАВА 11 Пулы потоков
В главе 8 мы обсудили синхронизацию потоков без перехода в режим ядра Замеча тельная особенность такой синхронизации — высокое быстродействие. И если Вы озабочены быстродействием потока, сначала подумайте, нельзя ли обойтись синхро низацией в пользовательском режиме.
Вы уже знаете, что создание многопоточных приложений — дело трудное. Вас подстерегают две серьезные проблемы: управление созданием и уничтожением по токов и синхронизация их доступа к ресурсам. Для решения второй проблемы в Windows предусмотрено множество синхронизирующих примитивовсобытия, сема форы, мьютексы, критические секции и др. Все они довольно просты в использова нии. Но если бы сисгема автоматически охраняла разделяемые ресурсы, вот тогда создавать многопоточные приложения было бы по-настоящемулегко Увы, операци онной системе Windows до этого еще далеко.
Проблему того, как управлять созданием и уничтожением потоков, каждый реша ет посвоему. За прошедшие годы я создал несколько реализаций пулов потоков, рас считанных на определенные сценарии. Однако в Windows 2000 появился ряд новых функций для операций с пулами потоков; эти функции упрощают создание, уничто жение и общий контроль за потоками. Конечно, встроенные в них механизмы носят общий характер и не годятся на все случаи жизни, но зачастую их вполне достаточ но, и они позволяют экономить массу времени при разработке многопоточного при ложения.
Эти функции дают возможность вызывать другие функции асинхронно, через определенные промежутки времени, при освобождении отдельных объектов ядра или при завершении запросов на асинхронный ввод-вывод.
Пул подразделяется на четыре компонента, которые описываются в таблице 11-1.
Компонент поддержки

|
|
ожидания |
ввода-вывода |
Других операций таймера |
|
|
|
|
|
Начальное |
Всегда 1 |
1 |
0 |
0 |
число |
|
|
|
|
потоков |
|
|
|
|
Когда поток |
При вызове первой |
Один поток |
В системе применяются эвристические методы, но на |
|
создается |
функции таймера пула |
для каждых |
создание потока влияют следующие факторы |
|
|
потоков |
63 зарегист |
• после добавления потока прошло определенное время |
|
|
|
рированных |
• установлен флаг WT_EXECUTELONGFUNCTION |
|
|
|
объектов |
• число элементов в очереди превышает пороговое |
|
|
|
|
значение |
|
|
|
|
|
|
Когда поток |
При завершении |
При отсутст |
При отсутствии у |
При простое потока в течение |
разрушается |
процесса |
вии зарегист |
потока текущих |
определен ного порогового |
|
|
рированных |
запросов на ввод- |
времени (около минусы) |
|
|
объектов |
вывод и простое в |
|
|
|
ожидания |
течение определенного |
|
|
|
|
порогового времени |
|
|
|
|
(около минуты) |
|
Как поток |
В |
WaitFor |
В "тревожном" |
GetQueued CompletionStatus |
ждет |
"тревожном"состоянии |
Multiple |
состоянии |
|
|
|
ObjectsEx |
|
|
|
|
|
|
|
Когда поток |
При освобожде нии |
При |
При посылке в очередь |
При поступлении запроса о |
пробуждается |
«ожидаемого таймера", |
освобождении |
АРС-вызова или |
статусе завершения или о |
|
кото рый посылает в |
объекта ядра |
завершении запроса на |
завершении ввода вывода (порт |
|
очередь АРС-вызов |
|
вводвывод |
завер шения требует, чтобы число |
|
|
|
|
потоков не превышало число |
|
|
|
|
процессоров более чем в 2 раза) |
|
|
|
|
|
Таблица 11-1. Компоненты поддержки пула потоков
При инициализации процесса никаких издержек, связанных с перечисленными в таблице компоненчами поддержки, не возникает. Однако, как только вызывается одна из функций пула потоков, для процесса создается набор этих компонентов, и неко торые из них сохраняются до его завершения. Как видите, издержки от применения этих функций отнюдь не малые: частью Вашего процесса становится целый набор потоков и внутренних структур данных. Так что, прежде чем пользоваться ими, тща тельно взвесьте все «за» и «против»
О'кэй, теперь, когда я Вас предупредил, посмотрим, как все это работает.
Сценарий 1: асинхронный вызов функций
Допустим, у Вас есть серверный процесс с основным потоком, который ждет клиен тский запрос. Получив его, он порождает отдельный поток для обработки этого зап роса Тем самым основной поток освобождается для приема следующего клиентского запроса. Такой сценарий типичен в клиент-серверных приложениях. Хотя он и так то незагейлив, при желании его можно реализовать с использованием новых функ ций пула потоков.
Получая клиентский запрос, основной поток вызывает:
BOOL QueueUserWorkItem( PTHREAD_START_ROUTINE pfnCallback, PVOID pvContext, ULONG dwFlags);
Эта функция помещает «рабочий элемент" (work item) в очсрсдь потока в пулс и тут же возвращает управление. Рабочий элемент — это просто вызов функции (па которую ссылается параметр pfnCallback), принимающей единственный параметр, pvContext. В конечном счете какой-то поток из пула займется обработкой этого эле
мента, в результате чего будет вызвана Ваша функция. У этой функции обратного вызова, за реализацию которой отвечаете Вы, должен быть следующий прототип:
DWORD WINAPI WorkItemFunc(PVOID pvContext);
Несмотря па то что тип возвращаемого значения определен как DWORD, на са мом деле оно игнорируется.
Обратите внимание, что Вы сами никогда не вызываете CreateThread Она вызы вается из пула потоков, автоматически создаваемого для Вашего процесса, а к функ ции WorkItemFunc обращается один из потоков этого пула. Кроме того, данный поток не уничтожается сразу после обработки клиентского запроса, а возвращается в пул, оставаясь готовым к обработке любых других элементов, помещаемых в очередь. Ваше приложение может стать гораздо эффективнее, так как Вам больше не придется со здавать и уничтожать потоки для каждого клиентского запроса. А поскольку потоки связаны с определенным портом завершения, количество одновременно работающих потоков не может превышать число процессоров более чем в 2 раза. За счет этого переключения контекста происходят реже.
Многое в пуле потоков происходит скрытно от разработчика: QueueUserWorkItem проверяет число потоков, включенных в сферу ответственности компонента поддер жки других операций (нс относящихся к вводу-выводу), и в зависимости от текущей нагрузки (количества рабочих элементов в очереди) может передать емудругие по токи. После этого QueueUserWorkltem выполняет операции, эквивалентные вызову PostQueuedCompletionStatus, пересылая информацию о рабочем элементе в порт за вершения ввода-вывода. В конечном счете поток, ждущий на этом объекте, извлекает Ваше сообщение (вызовом GetQueuedCompletionStatus} и обращается к Вашей функ ции. После того как она возвращает управление, поток вновь вызывает GetQueued ComplettonStatus, ожидая появления следующего рабочего элемента
Пул рассчитан на частую обработку асинхронного ввода-вывода — всякий раз, когда поток помещает в очередь запрос на ввод-вывод к драйверу устройства Пока драйвер выполняет его, поток, поставивший запрос в очередь, не блокируется и мо жет заниматься другой работой. Асинхронный ввод-вывод — ключ к созданию высо коэффективных, масштабируемых приложений, так как позволяет одному потоку обрабатывать запросы от множества клиентов по мере их поступления; ему не при ходится обрабатывать их последовательно или останавливаться, ожидая завершения ввода вывода.
Но Windows накладывает одно ограничение на запросы асинхронного ввода-вы вода, если поток, послав такой запрос драйверу устройства, завершается, данный зап рос теряется н никакие потоки о его судьбе не уведомляются. В хорошо продуман ном пуле, число потоков увеличивается и уменьшается в зависимости от потребнос тей его клиентов Поэтому, если поток посылает запрос и уничтожается из-за сокра щения пула, то уничтожается и этот запрос. Как правило, это не совсем то, что хоте лось бы, и здесь нужно найти какое-то решение.
Если Вы хотите поместить в очередь рабочий элемент, который выдает запрос на асинхронный ввод-вывод, то не сможете передать этот элемент компоненту поддер жки других операций в пуле потоков. Его примет лишь компонент поддержки ввода вывода. Последний включает набор потоков, которые не завершаются, пока есть хотя бы один запрос на ввод-вывод; поэтому для выполнения кода, выдающего запросы на асинхронный ввод-вывод, Вы должны пользоваться только этими потоками.
Чтобы передать рабочий элемент компоненту поддержки ввода-вывода, Вы може те попрежнему пользоваться функцией QueueUserWorkltem, но в параметре dwFlags
следует указать флаг WT_EXECUTEINIOTHREAD. А обычно Вы будете указывать в этом параметре флаг WT_EXECUTEDEFAULT (0) — он заставляет систему передать рабочий элемент компоненту поддержки других операций (не связанных с вводом-выводом).
В Windows есть функции (вроде RegNotifyChangeKeyValue), которые асинхронно выполняют операции, не относящиеся к вводу-выводу. Они также требуют, чтобы вызывающий поток не завершался преждевременно. С этой целью Вы можете исполь зовать флаг WT_EXECUTETNPERSISTENTTHREAD, который заставляет поток таймера выполнять поставленную в очередь функцию обратного вызова для рабочего элемен та. Так как этот компонент существует постоянно, асинхронная операция в конечном счете обязательно будет выполнена Вы должны позаботиться о том, чтобы функция обратного вызова выполнялась быстро и не блокировала работу компонента поддер жки таймера.
Хорошо продуманный пул должен также обеспечивать максимальную готовность потоков к обработке запросов. Если в пуле четыре потока, а в очереди сто рабочих элементов, то единовременно можно обработать только четыре элемента Это не проблема, если на обработку каждого элемента уходит лишь несколько миллисекунд, но в ином случае программа не сумеет своевременно обслуживать запросы.
Естественно, система не настолько умна, чтобы предвидеть, чем будет заниматься функция Вашего рабочего элемента, но если Вам заранее известно, что па это уйдет длительное время, вызовите QueueUserWorkltem с флагом WT_EXECUTELONGFUNC TION — он заставит пул создать новый поток, если остальные потоки будут в это вре мя заняты. Так, добавив в очередь 10 000 рабочих элементов (с флагом WT_EXECUTE LONGFUNCTION), Вы получите 10 000 новых потоков в пуле. Чтобы избежать этого, делайте перерывы между вызовами QueueUserWorkltem, и тогда часть потоков успсст завершиться до порождения новых.
Ограничение на количество потоков в пуле накладывать нельзя, иначе может воз никать взаимная блокировка потоков. Представьте очередь из 10 000 элементов, заб локированных 10 001-м и ждущих его освобождения. Установив предел в 10 000, Вы запретите выполнение 10001-го потока, и в результате целых 10 000 потоков оста нутся навечно заблокированными.
Используя функции пула, будьте осторожны, чтобы нс доводить дело до тупико вых ситуаций. Особую осторожность проявляйте, если функция Вашего рабочего эле мента использует критические секции, семафоры, мьютексы и др. — это увеличивает вероятность взаимной блокировки. Вы должны всегда точно знать, поток какого ком понента пула выполняет Ваш код. Также будьте внимательны, если функция рабочего элемента содержится в DLL, которая может быть динамически выгружена из памяти. Поток, вызывающий функцию из выгруженной DLL, приведет к нарушению доступа. Чтобы предотвратить выгрузку DLL при наличии рабочих элементов в очереди, со здайте