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

любой из них возвращается FALSE. Последующий вызов GetLastError дает

ERROR_CALL_NOT_IMPLEMENTED.

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

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

Когда пользователь работает с окнами какого-то процесса, последний считается ак тивным (foreground process), a остальные процессы — фоновыми (background proces ses).

Естественно, пользователь заинтересован в повышенной отзывчивости активно го процесса по сравнению с фоновыми Для этого Windows подстраивает алгоритм планирования потоков активного процесса, В Windows 2000, когда процесс становит

ся активным, система выделяет его потокам более длительные кванты времени Такая регулировка применяется только к процессам с классом приоритета normal

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

Переключатель Applications включает подстройку планировщика для активного процесса, а переключатель Background Services — выключает (в этом случае оптими зируется выполнение фоновых сервисов) В Windows 2000 Professional по умолчанию выбирается переключатель Applications, а в остальных версиях Windows 2000 — пе реключатель Background Services, так как серверы редко используются в интерактив ном режиме

Windows 98 тоже позволяет подстраивать распределение процессорного време ни для потоков активного процесса с классом приоритета normal Когда процесс этого класса становится активным, система повышает на 1 приоритет его потоков, если их исходные приоритеты были lowest, below normal, normal, above normal или highest, приоритет потоков idle или time-critical не меняется Поэтому поток с относительным приоритетом normal в активном процессе с классом приоритета normal имеет уро вень приоритета 9, а

не 8 Когда процесс вновь становится фоновым, приоритеты его потоков автоматически возвращаются к исходным уровням

WINDOWS 98

Windows 98 не предусматривает возможности настройки этого механизма, так как не рассчитана на работу в качестве выделенного сервера

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

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

Эта прогрдмма, "07 SchedLab ехе" (см листинг на рис 7-1), позволяет эксперименти ровать с классами приоритетов процессов и относительными приоритетами потоков

иисследовать их влияние на общую производительность системы. Файлы исходного кода

иресурсов этой программы находятся в каталоге 07-SchedLab нз компакт-диске, прилагаемом к книге После запуска SchedLab открывается окно, показанное ниже

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

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

операционной системы. Если Вы все же хотите поэкспериментировать с этим приоритетом, Вам придется модифицировать исход ный текст моей программы

Используя поле Sleep, можно приостановить первичный поток на заданное число миллисекунд в диапазоне oт 0 до 9999 Попробуйте приостанавливать его хотя бы на 1 мс и посмотрите, сколько процессорного времени это позволит сэкономить. На своем ноутбуке с процессором Pentium II 300 МГц, я выиграл аж 99% - впечатляет!

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

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

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

Привязка потоков к процессорам

По умолчанию Windows 2000 использует нежесткую привязку (soft affmity) потоков к процессорам Это означает, что при прочих равных условиях, система пытается выполнять поток на том же процессоре, на котором он работал в последний раз При таком подходе можно повторно использовать данные, все еще хранящиеся в кэше процессора

В повой компьютерной архитектуре NUMA (Non Uniform MemoryAccess) машина состоит из нескольких плат, на каждой из которых находятся четыре процессора и отдельный банк памяти На следующей иллюстрации показана машина с тремя таки ми платами, в сумме содержащими 12 процессоров Отдельный погок может выпол няться на любом из этих процессоров

Система NUMA достигает максимальной производительности, если процессоры используют память на своей плате Если же они обращаются к памяти на другой пла те, производительность резко падает В такой среде желательно, чтобы потоки одно го процесса выполнялись на процессорах 0-3, другого — на процессорах 4-7 и т д Windows 2000 позволяет подстроиться под эту архитектуру, закрепляя отдельные процессы и потоки за конкретными процессорами Иначе говоря, Вы можете конт ролировать, на каких процессорах будут выполняться Ваши потоки Такая привязка называется жесткой

(hard affmity)

Количество процессоров система определяет при загрузке, и эта информация ста новится доступной приложениям через функцию GetSystemInfo (о ней — в главе 14). По умолчанию любой поток может выполняться на любом процессоре. Чтобы пото ки отдельного процесса работали лишь на некоем подмножестве процессоров, ис пользуйте функцию SetProcessAffinityMask:

BOOL SetProcessAffinityMask( HANDLE hProcess, DWOHD_PTR dwProcessAffinityMask);

В первом параметре, hProcess, передается описатель процесса. Второй параметр, dwProcessAffinityMask, — это битовая маска, указывающая, на каких процессорах мо гут выполняться потоки данного процесса. Передав, например, значение 0x00000005, мы разрешим процессу использовать только процессоры 0 и 2 (процессоры 1 и 3-31 ему будут недоступны).

Привязка к процессорам наследуется дочерними процессами. Так, если для роди тельского процесса задана битовая маска 0x00000005, у всех потоков его дочерних процессов будет идентичная маска, и они смогут работать лишь на тех же процессо рах. Для привязки целой группы процессов к определенным процессорам используйте объект ядра "задание" (см главу 5).

Ну и, конечно же, есть функция, позволяющая получить информацию о такой привязке;

BOOL GetProcessAffinityMask( HANDLE hProcess, PDWORD_PTR pdwProcessAffiniLyMask, PDWORD_PTR pdwSystemAffinityMask);

Вы передаете ей описатель процесса, а результат возвращается в переменной, на которую указывает pdwProcessAffimtyMask. Кроме того, функция возвращает систем ную маску привязки через переменную, на которую ссылается pdwSystemAffinityMask. Эта маска указывает, какие процессоры в системе могут выполнять потоки. Таким образом, маска привязки процесса всегда является подмножеством системной маски привязки.

WINDOWS 98

В Windows 98, которая использует только один процессор независимо от того, сколько их на самом дслс, GetProcessAffinityMask всегда возвращает в обеих пе ременныхзначенис 1.

До сих пор мы говорили о том, как назначить все потоки процесса определенным процессорам. Но иногда такие ограничения нужно вводить для отдельных потоков. Допустим, в процессе имеется четыре потока, выполняемые на четырехпроцессорной машине. Один из потоков занимается особо важной работой, и Вы, желая повысить вероятность того, что у него всегда будет доступ к вычислительным мощностям, зап рещяете остальным потокам использовать процессор 0.

Задать маски привязки для отдельных потоков позволяет функция:

DWORD_PTR SetThreadAffimtyMask( HANOLE hThread, DWORD_PTR dwThreadAffinityMask);

В параметре hTbread передается описатель потока, a dwThreadAffinityMask опреде ляет процессоры, доступные этому потоку. Параметр dwThreadAffinityMask должен

быть корректным подмножеством маски привяжи процесса, которому принадлежит данный поток Функция возвращает предыдущую маску привязки потока Вот как ог раничить три потока из нашего примера процессорами 1, 2 и 3

//поток 0 выполняется только на процессоре 0

SetThreadAffimtyMask(hThread0 0x00000001);

//потоки 1, 2 3 выполняются на процессорах 1 2 3

SetThreadAffinityMask(hThredd1 0x0000000E); SetThreadAffimtyMask(hThread2 0x0000000E); SetThreadAffinityMask(hThread3 0x0000000E);

WINDOWS 98

В Windows 98, которая использует только один процессор независимо от того, сколько их на самом деле, параметр dwThreadAffmityMask всегда должен быть равен 1

При загрузке система тестирует процессоры типа x86 на наличие в них знамени того "жучка" в операциях деления чисел с плавающей точкой (эта ошибка имеется в некоторых Pentium) Она привязывает поток, выполняющий потенциально сбойную операцию деления, к исследуемому процессору и сравнивает результат с тем, что дол жно быть на самом деле Такая последовательность операций выполняется для каж дого процессора в машине

NOTE

В большинстве сред вмешательство в системную привязку потоков нарушает нормальную работу планировщика, не позволяя ему максимально эффектив но распределять вычислительные мощности Рассмотрим один пример

Поток

Приоритет

Маска привязки

Результат

 

 

 

 

А

4

0x00000001

Работает только на процессоре 0

 

 

 

 

В

8

0x00000003

Работает на процессоре 0 и 1

 

 

 

 

С

6

0x00000002

Работает только на процессоре 1

 

 

 

 

Когда поток А пробуждается, планировщик, видя, что тот жестко привязан к процессору 0, подключает сго именно к этому процессору Далее активизи руется поток В, который может выполняться на процессорах 0 и 1, и плани ровщик выделяет ему процессор 1, так как процессор 0 уже занят Пока все нормально

Но вот пробуждается поток С привязанный к процессору 1 Этот процес сор уже занят потоком В с приоритетом 8, а значит, поток С, приоритет кото рого равен 6, не может его вытеснить Он конечно, мог бы вытеснить поток А (с приоритетом 4) с процессора 0, но у него нет прав на использование этого процессора

Ограничение потока одним процессором не всегда является лучшим решением Всдь может оказаться так, что три потока конкурируют за доступ к процессору 0, тог да как процессоры 1, 2 и 3 простаивают Гораздо лучше сообщить системе, что поток желательно выполнять на определенном процессоре, но, если он занят, его можно переключать на другой процессор

Указать предпочтительный (идеальный) процессор позволяет функция:

DWORD SetThreadIdealProcessor( HANDLE hThread, DWORD dwIdealProcessor);

В параметре hThread передается описатель потока. D отличие от функций, кото рые мы уже рассматривали, параметр dwIdealProcessor содержит не битовую маску, а целое значение в диапазоне 0-31, которое указывает предпочтительный процессор для данного потока. Передав в нем константу MAXIMUM_PROCESSORS (в WinNT.h она определена как 32), Вы сообщите системе, что потоку не требуется предпочтитель ный процессор. Функция возвращает установленный ранее номер предпочтительно го процессора или MAXIMUM_PROCESSORS, если таковой процессор не задан

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

// загружаем ЕХЕ-файл в память

PLOADED_IMAGE pLoadedImage = ImageLoad(szExeName, NULL);

//получаем информацию о текущей загрузочной конфигурации ЕХЕфайла

IMAGE_LOAD_CONFIG_DIRECTORY ilcd; GetImageConfigInfprmation(pLoadedImage, &ilcd),

//изменяем маску привязки процесса

ilcd.ProcessAffimtyMask = 0x00000003;

//нам нужны процессоры 0 и 1

//сохраняем новую информацию о загрузочной конфигурации

SetImageConfigInformation(pLoadedImage, &ilcd);

//выгружаем ЕХЕ-файл из памяти

ImageUnload(pLoadcdImage);

Детально описывать эти функции я не стану — при необходимости Вы найдете их в документации Platform SDK. Кроме того, Вы можете использовать утилиту Image Cfg.exe,

которая позволяет изменять некоторые флаги в заголовке исполняемого мо дуля Подсказку по ее применению Вы получите, запустив ImageCfg.exe без ключей.

Указав при запуске ImageCfg ключ -а, Вы сможете изменить маску привязки для приложения Конечно, все, что делает эта утилита, — вызывает функции, перечислен ные в подсказке по ее применению. Обратите внимание на ключ -u, который сооб щает системе, что исполняемый файл может выполняться исключительно на одно процессорной машине

И, наконец, привязку процесса к процессорам можно изменять с помощью Task Manager в Windows 2000, В многопроцессорных системах в контекстном меню для процесса появляется команда Set Affinity (ее нет на компьютерах с одним процессо ром) Выбрав эту команду, Вы откроете показанное ниже диалоговое окпо и выберете конкретные процессоры для данногопроцесса.

WINDOWS 2000

При запуске Windows, 2000 на машине с процессорами типа x86 можно огра ничить число процессоров, используемых системой. В процессе загрузки сис тема считывает файл Boot.ini, который находится в корневом каталоге загру зочного диска. Вот как он выглядит на моем компьютере с двумя процессорами

[boot loader] timeout=2

default=multi(0)disk(0)rdisk(0)partition(1)\WINNT

[operating systems]

multi(0)disk(0)rdisk(0)partition(1)\WINNT= "Windows 2000 Server" /fastdetecL multi(0)disk(0)rdisk(0)partition(1)\WINNT="Windows 2000 Server" /fastdetec+ /NurnProcs=1

Этот файл создается при установке Windows 2000, последнюю запись я добавил сам (с помощыо Notepad) Она заставляет систему использовать только один процессор Ключ /NumProcs=l — как раз то зелье, которое и вызывает все эти магические превращения Я пользуюсь им иногда для отладки (Но обычно работаю со всеми своими процессорами)

Заметьте, что ключи перенесены на отдельные строки с отступом лишь для удобства чтения На самом деле ключи и путь от загрузочного раздела жестко го диска должны находиться на одной строке