Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6266
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

260 Часть II. Приступаем к работе

Табл. 7-4. Формирование уровней процесса

 

 

 

Класс приоритета процесса

 

Относительный приоритет потока

Idle

Below

 

Normal

Above

High

Real-time

 

 

normal

 

 

normal

 

 

Time-critical (критичный по времени)

15

15

 

15

15

15

31

Highest (высший)

6

8

 

10

12

15

26

Above normal (выше обычного)

5

7

 

9

11

14

25

Normal (обычный)

4

6

 

8

10

13

24

Below normal (ниже обычного)

3

5

 

7

9

12

23

Lowest (низший)

2

4

 

6

8

11

22

Idle (простаивающий)

1

1

 

1

1

1

16

Обратите внимание, что в таблице не показано, как задать уровень приоритета 0. Это связано с тем, что нулевой приоритет зарезервирован для потока обнуления страниц, и никакой другой поток не может иметь такой приоритет. Кроме того, уровни 17-21 и 27-30 в обычном приложении тоже недоступны. Вы можете пользоваться ими, только если пишете драйвер устройства, работающий в режиме ядра. И еще одно: уровень приоритета потока в процессе с классом real-time не может опускаться ниже 16, а потока в процессе с любым другим классом — подниматься выше 15.

Примечание. Концепция класса приоритета вводит некоторых в заблуждение. Они делают отсюда вывод, будто процессы участвуют в распределении процессорного времени. Так вот, процессы никогда не получают процессорное время — оно выделяется лишь потокам. Класс приоритета процесса — сугубо абстрактная концепция, введенная Майкрософт с единственной целью: скрыть от разработчика внутреннее устройство планировщика.

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

Глава 7. Планирование потоков, приоритет и привязка к процессорам.docx 261

Программирование приоритетов

Так как же процесс получает класс приоритета? Очень просто. Вызывая CreateProcess, вы можете указать в ее параметре fdwCreate нужный класс приоритета. Идентификаторы этих классов приведены в таблице 7-5.

Табл. 7-5. Классы приоритета процессов

Класс приоритета

Идентификатор

Real-time

REALTIME_PRIORITY_CLASS

High

HIGH_PRIORITY_CLASS

Above normal

ABOVENORMALPRIORITYCLASS

Normal

NORMAL_PRIORITY_CLASS

Below normal

BELOW_NORMAL_PRIORITY_CLASS

Idle

IDLE_PRIORITY_CLASS

Вам может показаться странным, что, создавая дочерний процесс, родительский сам устанавливает ему класс приоритета. За примером далеко ходить не надо — возьмем все тот же Explorer. При запуске из него какого-нибудь приложения новый процесс создается с обычным приоритетом. Но Explorer ведь не знает, что делает этот процесс и как часто его потокам надо выделять процессорное время. Поэтому в системе предусмотрена возможность изменения класса приоритета самим выполняемым процессом — вызовом функции SetPriorityClass:

BOOL SetPriorityClass(

HANDLE hProcess,

DWORD fdwPriority);

Эта функция меняет класс приоритета процесса, определяемого описателем hProcess, в соответствии со значением параметра fdwPriority. Последний должен содержать одно из значений, указанных в таблице выше. Поскольку SetPriorityClass принимает описатель процесса, вы можете изменить приоритет любого процесса, выполняемого в системе, — если его описатель известен и у вас есть соответствующие права доступа.

Обычно процесс пытается изменить свой класс приоритета. Вот как процесс может сам себе установить класс приоритета idle:

BOOL SetPriorityClass( GetCurrentProcess(), IDLE_PRI0RITY_CLA8S);

Парная ей функция GetPriorityClass позволяет узнать класс приоритета любого процесса:

DWORD GetPriorityClass(HANDLE hProcess);

Она возвращает, как вы догадываетесь, один из ранее перечисленных флагов.

262 Часть II. Приступаем к работе

При запуске из оболочки командного процессора начальный приоритет программы тоже обычный. Однако, запуская ее командой Start, можно указать ключ, определяющий начальный приоритет. Так, следующая команда, введенная в оболочке командного процессора, заставит систему запустить приложение Calculator и присвоить ему приоритет idle:

C:\>START /LOW CALC.EXE

Команда Start допускает также ключи /BELOWNORMAL, /NORMAL, /ABOVENORMAL, /HIGH и /REALTIME, позволяющие начать выполнение программы с соответствующим классом приоритета. Разумеется, после запуска программа может вызвать SetPriorityClass и установить себе другой класс приоритета.

Task Manager в Windows дает возможность изменять класс приоритета процесса. На рисунке ниже показана вкладка Processes в окне Task Manager со списком выполняемых на данный момент процессов. В колонке Base Pri сообщается класс приоритета каждого процесса. Вы можете изменить его, выбрав процесс и указав другой класс в подменю Set Priority контекстного меню.

Только что созданный поток получает относительный приоритет normal. Почему CreateThread не позволяет задать относительный приоритет — для меня так и остается загадкой. Такая операция осуществляется вызовом функции:

B00L SetThreadPriority(

HANDLE hThread, int nPriority);

Глава 7. Планирование потоков, приоритет и привязка к процессорам.docx 263

Разумеется, параметр hTheard указывает на поток, чей приоритет вы хотите изменить, а через nPriority передается один из идентификаторов (см. таблицу 7-6).

Табл. 7-6. Относительные приоритеты потоков

Относительный

Идентификатор

приоритет потока

 

Time-critical

THREAD_PRIORITY_TIME_CRITICAL

Highest

THREAD_PRIORITY_HIGHEST

Above normal

THREAD_PRIORITY_ABOVE_NORMAL

Normal

THREAD_PRIORITY_NORMAL

Below normal

THREAD_PRIORITY_BELOW_NORMAL

Lowest

THREAD_PRIORITY_LOWEST

Idle

THREAD_PRIORITY_IDLE

Функция GetThreadPriority, парная SetThreadPriority, позволяет узнать отно-

сительный приоритет потока:

int GetThreadPriority(HANDLE hThread);

Она возвращает один из идентификаторов, показанных в таблице выше.

Чтобы создать поток с относительным приоритетом idle, сделайте, например, так:

DWORD dwThreadID;

HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, NULL,

CREATE_SUSPENDED, adwThreadlD);

SetThreadPriority(hThread, THREAD_PRIORITY_IDLE);

ResumeThread(hThread);

CloseHandle(hThread);

Заметьте, что CreateThread всегда создает поток с относительным приоритетом normal. Чтобы присвоить потоку относительный приоритет idle, создайте приостановленный поток, передав в CreateThread флаг CREATE_SUSPENDED, а потом вызовите SetThreadPriority и установите нужный приоритет. Далее можно вызвать ResumeThread, и поток будет включен в число планируемых. Сказать заранее, когда поток получит процессорное время, нельзя, но планировщик уже учитывает его новый приоритет. Выполнив эти операции, вы можете закрыть описатель потока, чтобы соответствующий объект ядра был уничтожен по завершении данного потока.

Примечание. Ни одна Windows-функция не возвращает уровень приоритета потока. Такая ситуация создана преднамеренно. Вспомните, что Майкрософт может в любой момент изменить алгоритм распределения процессорного времени. Поэтому при разработке приложений не сто-

264 Часть II. Приступаем к работе

ит опираться на какие-то нюансы этого алгоритма. Используйте классы приоритетов процессов и относительные приоритеты потоков, и ваши приложения будут нормально работать как в нынешних, так и в следующих версиях Windows.

Динамическое изменение уровня приоритета потока

Уровень приоритета, получаемый комбинацией относительного приоритета потока и класса приоритета процесса, которому принадлежит данный поток, называют базовым уровнем приоритета потока. Иногда система изменяет уровень приоритета потока. Обычно это происходит в ответ на некоторые события, связанные с вводом-выводом (например, на появление оконных сообщений или чтение с диска).

Так, поток с относительным приоритетом normal, выполняемый в процессе с классом приоритета high, имеет базовый приоритет 13. Если пользователь нажимает какую-нибудь клавишу, система помещает в очередь потока сообщение WM_KEYDOWN. А поскольку в очереди потока появилось сообщение, поток становится планируемым. При этом драйвер клавиатуры может заставить систему временно поднять уровень приоритета потока с 13 до 15 (действительное значение может отличаться в ту или другую сторону).

Процессор исполняет поток в течение отведенного отрезка времени, а по его истечении система снижает приоритет потока на 1, до уровня 14. Далее потоку вновь выделяется квант процессорного времени, по окончании которого система опять снижает уровень приоритета потока на 1. И теперь приоритет потока снова соответствует его базовому уровню.

Текущий уровень приоритета не может быть ниже базового. Кроме того, драйвер устройства, «разбудивший» поток, сам устанавливает величину повышения приоритета. И опять же Майкрософт не документирует, насколько повышаются эти значения конкретными драйверами. Таким образом, она получает возможность тонко настраивать динамическое изменение приоритетов потоков в операционной системе, чтобы та максимально быстро реагировала на действия пользователя.

Система повышает приоритет только тех потоков, базовый уровень которых находится в пределах 1-15. Именно поэтому данный диапазон называется «областью динамического приоритета» (dynamic priority range). Система не допускает динамического повышения приоритета потока до уровней реального времени (более 15). Поскольку потоки с такими уровнями обслуживают системные функции, это ограничение не дает приложению нарушить работу операционной системы. И, кстати, система никогда не меняет приоритет потоков с уровнями реального времени (от 16 до 3-1).

Некоторые разработчики жаловались, что динамическое изменение приоритета системой отрицательно сказывается на производительности их приложений, и поэтому Майкрософт добавила две функции, позволяющие отключать этот механизм:

Глава 7. Планирование потоков, приоритет и привязка к процессорам.docx 265

BOOL SetProcessPriorityBoost(

HANDLE hProcess,

BOOL bDisablePriorityBoost);

BOOL SetThreadPriorityBoost(

HANDLE hThread,

BOOL bDisablePriorityBoost);

SetProcessPriorityBoost заставляет систему включить или отключить изменение приоритетов всех потоков в указанном процессе, а SetThreadPriorityBoost действует применительно к отдельным потокам. Эти функции имеют свои аналоги, позволяющие определять, разрешено или запрещено изменение приоритетов:

BOOL GetProcessPriorityBoost(

HANDLE hProcess,

PBOOL pbDisablePriorityBoost);

BOOL GetThreadPriorityBoost(

HANDLE hThread,

PBOOL pbDisablePriorityBoost);

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

Есть еще одна ситуация, в которой система динамически повышает приоритет потока. Представьте, что поток с приоритетом 4 готов к выполнению, но не может получить доступ к процессору из-за того, что его постоянно занимают потоки с приоритетом 8. Это типичный случай «голодания» потока с более низким приоритетом. Обнаружив такой поток, не выполняемый на протяжении уже трех или четырех секунд, система поднимает его приоритет до 15 и выделяет ему двойную порцию времени. По его истечении потоку немедленно возвращается его базовый приоритет.

Подстройка планировщика для активного процесса

Когда пользователь работает с окнами какого-то процесса, последний считается

активным (foreground process), а остальные процессы — фоновыми (background processes). Естественно, пользователь заинтересован в повышенной отзывчивости активного процесса по сравнению с фоновыми., Для этого Windows подстраивает алгоритм планирования потоков активного процесса. В Windows 2000, когда процесс становится активным, система выделяет его потокам более длительные кванты времени. Такая регулировка применяется только к процессам с классом приоритета normal.

Windows Vista позволяет модифицировать работу этого механизма подстройки. Щелкнув кнопку Performance Options на вкладке Advanced диалогового окна System Properties, вы открываете следующее окно.

266 Часть II. Приступаем к работе

Если пользователь выбирает оптимизацию работы программ (это настройка по умолчанию в Windows Vista), система выполняет подстройку. Если же пользователь предпочитает оптимизацию работы фоновых служб, никакой подстройки не выполняется.

Приоритеты запросов ввода-вывода

От расстановки приоритетов потоков зависит распределение между ними времени процессора. Но потоки исполняют, наряду с вычислениями, запросы дискового ввода-вывода. Так, поток с низким приоритетом, получивший время процессора, за очень короткое время может поставить в очередь тысячи запросов вводавывода. Поскольку операции ввода-вывода требуют сравнительно много времени, такой низкоприоритетный поток способен существенно снизить отзывчивость системы, приостановив исполнение высокоприоритетных потоков. Именно в этом причина «торможения» системы во время работы низкоприоритетных служб, таких как утилиты для дефрагментации диска, антивирусов, индексирующих программ и т.п.

Теперь в Windows Vista потоки могут задавать приоритеты запросов ввода-вывода. Так, можно сообщить Windows, что данный поток вправе генерировать только низкоприоритетные запросы ввода-вывода, вызвав

SetTnreadPriority с передачей флага THREAD_MODE_BACKGROUND_ BEGIN. Заметьте, что при этом понижается и приоритет потока в распределении процессорного времени. Восстановить способность потока генериро-

Глава 7. Планирование потоков, приоритет и привязка к процессорам.docx 267

вать обычные запросы ввода-вывода (и вернуть ему обычный приоритет для планировщика) можно повторным вызовом SetThreadPriority, но с передачей

THREAD_MODE_BACKGROUND_END. При вызове SetThreadPriority с любым из этих флагов необходимо также передавать описатель вызывающего потока (его можно получить, вызвав GetCurrentThread). Потокам запрещено изменять приоритет операций ввода-вывода других потоков.

Чтобы понизить приоритет в отношении ввода-вывода и распределении процессорного времени для всех потоков процесса, следует вызвать SetPriorityClass c передачей флага PROCESS_MODE_BACKGROUND_BEGIN. Вернуть процесс в исходное состояние можно вызовом SetPriorityClass с передачей

PROCESS_MODE_BACKGROUND_END. При вызове SetPriorityClass с любым из этих флагов необходимо также передавать описатель вызывающего процесса (его можно получить, вызвав GetCurrentProcess). Потокам запрещено изменять приоритет операций ввода-вывода потоков в других процессах.

Возможна и более тонкая настройка приоритета запросов ввода-вывода. Так, поток, обладающий нормальным приоритетом, может генерировать низкоприоритетные запросы ввода-вывода к отдельному файлу. Вот как это делается:

FILE_IO_PRIORITY_HINT_INFO phi; phi.PriorityHint = IoPriorityHintLow; SetFileInformationByHandle(

hFile, FileIoPriorityHintInfo, &phi, sizeof(PriorityHint));

Приоритет, заданный вызовом SetFileInformationByHandle, заменяет приоритет, заданный на уровне процесс или потока вызовом SetPriorityClass или SetThreadPriority, соответственно.

Как разработчик, вы отвечаете за использование этих механизмов для поддержания отзывчивости активных приложений, а для этого необходимо избегать инверсии приоритетов (priority inversion). Так, если потоки с нормальным приоритетом выполняют много операций ввода-вывода, низкоприоритетные могут ожидать исполнения своих запросов на чтение и запись в течение нескольких секунд. Если же низкоприоритетный поток заблокирует ресурс и потокам с нормальным приоритетом придется ждать его освобождения, дело закончится тем, что приоритетные потоки будут ждать завершения запросов ввода-вывода, сгенерированных фоновыми потоками. Более того, такая ситуация может возникнуть, даже если фоновые потоки не сгенерировали ни одного запроса ввода-вывода. В силу этих причин совместное использование синхронизирующих объектов потоками с нормальным и низким уровнем приоритета следует свести к минимуму (а лучше вовсе избегать его) — так можно предотвратить инверсию приоритетов, когда потокам с нормальным приоритетом приходится ждать освобождения блокировок, установленных потоками с фоновыми уровнями приоритета.

268 Часть II. Приступаем к работе

Примечание Описанные выше механизмы используются функцией SuperFetch. Подробнее об этом см. По ссылке http://www.microsoft.com/whdc/ driver/priorityio.mspx

Программа-пример Scheduling Lab

Эта программа, «07 SchedLab.exe» (см. листинг ниже), позволяет экспериментировать с классами приоритетов процессов и относительными приоритетами потоков и исследовать их влияние на общую производительность системы. Файлы исходного кода и ресурсов этой программы находятся в каталоге 07-SchedLab на компакт-диске, прилагаемом к книге. После запуска SchedLab открывается окно, показанное ниже.

Изначально первичный поток работает очень активно, и степень использования процессора подскакивает до 100%. Все, чем он занимается, — постоянно увеличивает исходное значение на 1 и выводит текущее значение в крайнее справа окно списка. Все эти числа не несут никакой смысловой информации; их появление просто демонстрирует, что поток чем-то занят. Чтобы прочувствовать, как повлияет на него изменение приоритета, запустите по крайней мере два экземпляра программы. Можете также открыть Task Manager и понаблюдать за нагрузкой на процессор, создаваемой каждым экземпляром.

В начале теста процессор будет загружен на 100%, и вы увидите, что все экземпляры SchedLab получают примерно равные кванты процессорного времени. (Task Manager должен показать практически одинаковые процентные доли для всех ее экземпляров.) Как только вы поднимете класс приоритета одного из экземпляров до above normal или high, львиную долю процессорного времени начнет получать именно этот экземпляр, а аналогичные показатели для других экземпляров резко упадут. Однако они никогда не опустятся до нуля — это действует механизм динамического повышения приоритета «голодающих» процессов. Теперь вы можете самостоятельно поиграть с изменением классов приоритетов процессов и относительных приоритетов потоков. Возможность установки класса приоритета real-time я исключил намеренно, чтобы не нарушить работу операционной системы. Если вы все же хотите поэкспериментировать с этим приоритетом, вам придется модифицировать исходный текст моей программы.

Используя поле Sleep, можно приостановить первичный поток на заданное число миллисекунд в диапазоне от 0 до 9999. Попробуйте приостанав-

Глава 7. Планирование потоков, приоритет и привязка к процессорам.docx 269

ливать его хотя бы на 1 мс и посмотрите, сколько процессорного времени это позволит сэкономить. На своем ноутбуке с 2,2-ГГц процессором Pentium я выиграл аж 99% — впечатляет!

Кнопка Suspend заставляет первичный поток создать дочерний поток, который приостанавливает родительский и выводит следующее окно.

Пока это окно открыто, первичный поток полностью отключается от процессора, а дочерний тоже не требует процессорного времени, так как ждет от пользователя дальнейших действий. Вы можете свободно перемещать это окно в пределах экрана или убрать его в сторону от основного окна программы. Поскольку первичный поток остановлен, основное окно не принимает оконных сообщений (в том числе WM_PAINT), Это еще раз доказывает, что поток задержан. Закрыв окно с сообщением, вы возобновите первичный поток, и нагрузка на процессор снова возрастет до 100%.

А теперь проведите еще один эксперимент. Откройте диалоговое окно Performance Options (я говорил о нем в предыдущем разделе) и выберите переключатель Background Services (или, наоборот, Application). Потом запустите несколько экземпляров моей программы с классом приоритета normal и выберите один из них, сделав его активным процессом. Вы сможете наглядно убедиться, как эти переключатели влияют на активные и фоновые процессы.

SchedLab.cpp

/****************************************************************************** Module: SchedLab.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#include "..\CommonFiles\CmnHdr.h" /* See Appendix A. */ #include <windowsx.h>

#include <tchar.h> #include "Resource.h" #include <StrSafe.h>

///////////////////////////////////////////////////////////////////////////////

DWORD WINAPI ThreadFunc(PVOID pvParam) {

Соседние файлы в предмете Программирование на C++