Издание четвертое windows ® для профессионалов создание эффективных Win32-приложений с учетом специфики 64-разрядной версии Windows



Pdf просмотр
страница20/68
Дата28.11.2016
Размер3.57 Mb.
Просмотров12519
Скачиваний0
1   ...   16   17   18   19   20   21   22   23   ...   68
Г ЛАВА 7
Планирование потоков, приоритет и привязка к процессорам
Этот код, вероятно, приведет к ошибке защиты (нарушению доступа) в удаленном потоке система сообщит о необработанном исключении, и удаленный процесс будет закрыт. Все верно — не Ваша удаленный. Вы благополучно обрушили другой процесс, оставив свой в целости и сохранности!
Функции
GetThreadContext и SetThreadContext наделяют Вас огромной властью над потоками, но пользоваться ею нужно с осторожностью. Вызывают их лишь считанные приложения. Эти функции предназначены для отладчиков и других инструментальных средств, хотя обращаться к ним можно из любых программ.
Подробнее о структуре CONTEXT мы поговорим в главе 24.
Приоритеты потоков
В начале главы я сказал, что поток получает доступ к процессору на 20 мс, после чего планировщик переключает процессор на выполнение другого потока. Так происходит, только если у всех потоков один приоритет, нона самом деле в системе существуют потоки с разными приоритетами, а это меняет порядок распределения процессорного времени.
Каждому потоку присваивается уровень приоритета — от 0 (самый низкий) до самый высокий. Решая, какому потоку выделить процессорное время, система сначала рассматривает только потоки с приоритетом 31 и подключает их к процессору по принципу карусели. Если поток с приоритетом 31 не исключен из планирования,
он немедленно получает квант времени, по истечении которого система проверяет,
есть ли еще один такой поток. Если да, он тоже получает свой квант процессорного времени.
Пока в системе имеются планируемые потоки с приоритетом 31, ни один поток с более низким приоритетом процессорного времени не получает. Такая ситуация называется голоданием (starvation). Она наблюдается, когда потоки с более высоким приоритетом так интенсивно используют процессорное время, что остальные практически не работают. Вероятность этой ситуации намного ниже в многопроцессорных системах, где потоки с приоритетами 31 и 30 могут выполняться одновременно.
Система всегда старается, чтобы процессоры были загружены работой, и они простаивают только в отсутствие планируемых потоков.
На первый взгляд, в системе, организованной таким образом, у потоков с низким приоритетом нет ни единого шанса на исполнение. Но, как я уже говорил, зачастую потоки как разине нужно выполнять. Например, если первичный поток Вашего процесса вызывает
GetMessage, а система видит, что никаких сообщений пока нет, она приостанавливает его выполнение, отнимает остаток неиспользованного времени и тут же подключает к процессору другой ожидающий поток. И пока в системе не появятся сообщения для потока Вашего процесса, он будет простаивать — система не станет тратить на него процессорное время. Но вот в очереди этого потока появляется сообщение, и система сразу же подключает его к процессору (если только в этот момент не выполняется поток с более высоким приоритетом).
А теперь обратите внимание на еще один момент. Потоки с более высоким приоритетом всегда вытесняют потоки с более низким приоритетом независимо от того,
исполняются последние или нет. Допустим, процессор исполняет поток с приоритетом, и тут система обнаруживает, что поток с более высоким приоритетом готов к выполнению. Что будет Система остановит поток с более низким приоритетом даже если не истек отведенный ему квант процессорного времени — и подключит к процессору поток с более высоким приоритетом (и, между прочим, выдаст ему полный квант времени).

168
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
Кстати, при загрузке системы создается особый поток —
поток обнуления стра
ниц (zero page thread), которому присваивается нулевой уровень приоритета. Ни один поток, кроме этого, не может иметь нулевой уровень приоритета. Он обнуляет свободные страницы в оперативной памяти при отсутствии других потоков, требующих внимания со стороны системы.
Абстрагирование приоритетов
Создавая планировщик потоков, разработчики из Microsoft прекрасно понимали, что он не подойдет на все случаи жизни. Они также осознавали, что со временем назначение компьютера может измениться. Например, в момент выпуска Windows NT создание приложений с поддержкой OLE еще только начиналось. Теперь такие приложения обычное дело. Кроме того, значительно расширилось применение игрового программного обеспечения, ну и, конечно же, Интернета.
Алгоритм планирования потоков существенно влияет на выполнение приложений.
С самого начала разработчики Microsoft понимали, что его придется изменять по мере того, как будут расширяться сферы применения компьютеров. Microsoft гарантирует,
что наши программы будут работать ив следующих версиях Windows. Как же ей удается изменять внутреннее устройство системы, не нарушая работоспособность наших программ Ответ в том, что:
í
планировщик документируется не полностью не разрешает в полной мере использовать все особенности планировщика предупреждает, что алгоритм работы планировщика постоянно меняется, и не рекомендует писать программы в расчете на текущий алгоритм API предоставляет слой абстрагирования от конкретного алгоритма работы планировщика, запрещая прямое обращение к планировщику. Вместо этого Вы вызываете функции Windows, которые интерпретируют Ваши параметры в зависимости от версии системы. Я буду рассказывать именно об этом слое абстрагирования.
Проектируя свое приложение, Вы должны учитывать возможность параллельного выполнения других программ. Следовательно, Вы обязаны выбирать класс приоритета, исходя из того, насколько отзывчивой должна быть Ваша программа. Согласен,
такая формулировка довольно туманна, но таки задумано Microsoft не желает обещать ничего такого, что могло бы нарушить работу Вашего кода в будущем поддерживает шесть классов приоритета idle (простаивающий, below normal (ниже обычного, normal (обычный, above normal (выше обычного, high (высокий) и realtime (реального времени. Самый распространенный класс приоритета,
естественно, — normal; его использует 99% приложений. Классы приоритета показаны в следующей таблице.
Класс приоритета
Описание
Real Потоки в этом процессе обязаны немедленно реагировать на события,
обеспечивая выполнение критических повремени задач. Такие потоки вытесняют даже компоненты операционной системы. Будьте крайне осторожны с этим классом.
High
Потоки в этом процессе тоже должны немедленно реагировать на события, обеспечивая выполнение критических повремени задач. Этот класс присвоен, например, Task Manager, что дает возможность пользователю закрывать больше неконтролируемые процессы.

169
Г ЛАВА 7
Планирование потоков, приоритет и привязка к процессорам
продолжение
Класс приоритета
Описание
Above Класс приоритета, промежуточный между normal и high. Это новый класс, введенный в Windows Потоки в этом процессе не предъявляют особых требований к выделению им процессорного времени Класс приоритета, промежуточный между normal и idle. Это новый класс, введенный в Windows Потоки в этом процессе выполняются, когда система незанята другой работой. Этот класс приоритета обычно используется для утилит, работающих в фоновом режиме, экранных заставок и приложений, собирающих статистическую информацию.
Приоритет idle идеален для программ, выполняемых, только когда системе больше нечего делать. Примеры таких программ — экранные заставки и средства мониторинга. Компьютер, неиспользуемый в интерактивном режиме, может быть занят другими задачами (действуя, скажем, в качестве файлового сервера, и их потокам незачем конкурировать с экранной заставкой за доступ к процессору. Средства мониторинга, собирающие статистическую информацию о системе, тоже не должны мешать выполнению более важных задач.
Класс приоритета high следует использовать лишь при крайней необходимости.
Может, Вы этого и не знаете, но Explorer выполняется с высоким приоритетом. Бо
' ль шую часть времени его потоки простаивают, готовые пробудиться, как только пользователь нажмет какую нибудь клавишу или щелкнет кнопку мыши. Пока потоки Explorer простаивают, система не выделяет им процессорное время, что позволяет выполнять потоки с более низким приоритетом. Но вот пользователь нажал, скажем, Ctrl+Esc, и система пробуждает поток Explorer. (Комбинация клавиш Ctrl+Esc попутно открывает меню Start.) Если в данный момент исполняются потоки с более низким приоритетом, они немедленно вытесняются, и начинает работать поток Explorer. Microsoft разработала Explorer именно так потому, что любой пользователь — независимо от текущей ситуации в системе — ожидает мгновенной реакции оболочки на свои команды. В сущности, окна Explorer можно открывать, даже когда все потоки с более низким приоритетом зависают в бесконечных циклах. Обладая более высоким приоритетом, потоки Explorer вытесняют поток, исполняющий бесконечный цикли дают возможность закрыть зависший процесс.
Надо отметить высокую степень продуманности Explorer. Основную часть времени он просто спит, не требуя процессорного времени. Будь это не так, вся система работала бы гораздо медленнее, а многие приложения просто не отзывались бы на действия пользователя.
Классом приоритета real time почти никогда не стоит пользоваться. На самом деле в ранних бета версиях Windows NT 3.1 присвоение этого класса приоритета приложениям даже не предусматривалось, хотя операционная система поддерживала эту возможность. Real time — чрезвычайно высокий приоритет, и, поскольку большинство потоков в системе (включая управляющие самой системой) имеет более низкий приоритет, процесс с таким классом окажет на них сильное влияние. Так, потоки реального времени могут заблокировать необходимые операции дискового и сетевого ввода вывода и привести к несвоевременной обработке ввода от мыши и клавиатуры пользователь может подумать, что система зависла. У Вас должна быть очень веская причина для применения класса real time — например, программе требуется

170
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
реагировать на события в аппаратных средствах с минимальной задержкой или выполнять быстротечную операцию, которую нельзя прерывать ни при каких обстоя тельствах.
Процесс с классом приоритета real time нельзя запустить, если пользователь не имеет привилегии Increase Scheduling Priority. По умолчанию такой привилегией обладает администратор и пользователь с расширенными полномочиями.
Конечно, большинство процессов имеет обычный класс приоритета. В Windows появилось два новых промежуточных класса — below normal и above normal. Microsoft добавила их, поскольку некоторые компании жаловались, что существующий набор классов приоритетов не дает должной гибкости.
Выбрав класс приоритета, забудьте о том, как Ваша программа будет выполняться совместно с другими приложениями, и сосредоточьтесь на ее потоках. Windows поддерживает семь относительных приоритетов потоков idle (простаивающий, низший, below normal (ниже обычного, normal (обычный, above normal (выше обычного, highest (высший) и time critical (критичный повремени. Эти приоритеты относительны классу приоритета процесса. Как обычно, большинство потоков использует обычный приоритет. Относительные приоритеты потоков описаны в следующей таблице.
Относительный приоритет потока
Описание
Time Поток выполняется с приоритетом 31 в классе real time и с приоритетом 15 в других классах
Highest
Поток выполняется с приоритетом на два уровня выше обычного для данного класса Поток выполняется с приоритетом на один уровень выше обычного для данного класса
Normal
Поток выполняется с обычным приоритетом процесса для данного класса Поток выполняется с приоритетом на один уровень ниже обычного для данного класса
Lowest
Поток выполняется с приоритетом на два уровня ниже обычного для данного класса
Idle
Поток выполняется с приоритетом 16 в классе real time и с приоритетом 1 в других классах
Итак, Вы присваиваете процессу некий класс приоритета и можете изменять относительные приоритеты потоков в пределах процесса. Заметьте, что я не сказал ни слова об уровнях приоритетов 0–31. Разработчики приложений не имеют сними дела.
Уровень приоритета формируется самой системой, исходя из класса приоритета процесса и относительного приоритета потока. А механизм его формирования — как раз то, чем Microsoft не хочет себя ограничивать. И действительно, этот механизм меняется практически в каждой версии системы.
В следующей таблице показано, как формируется уровень приоритета в Win dows 2000, ноне забывайте, что в Windows NT и тем более в Windows 95/98 этот механизм действует несколько иначе. Учтите также, что в будущих версиях Windows он вновь изменится.
Например, обычный поток в обычном процессе получает уровень приоритета Поскольку большинство процессов имеет класса большинство потоков —

171
Г ЛАВА 7
Планирование потоков, приоритет и привязка к процессорам относительный приоритету основной части потоков в системе уровень приоритета равен Обычный поток в процессе с классом приоритета high получает уровень приоритета. Изменив класс приоритета процесса на idle, Вы снизите уровень приоритета того же потока до 4. Вспомните, что приоритет потока всегда относителен классу приоритета его процесса. Изменение класса приоритета процесса не влияет на относительные приоритеты его потоков, но сказывается на уровне их приоритета.
Класс приоритета процесса
Относительный
Below
Above приоритет потока normal
Normal normal
High
Real-time
Time critical (критичный повремени (высший 8
10 12 15 26
Above normal (выше обычного 7
9 11 14 25
Normal (обычный 6
8 10 13 24
Below normal (ниже обычного 5
7 9
12 23
Lowest (низший 4
6 8
11 22
Idle (простаивающий 1
1 1
1 Обратите внимание, что в таблице не показано, как задать уровень приоритета Это связано стем, что нулевой приоритет зарезервирован для потока обнуления страниц, и никакой другой поток не может иметь такой приоритет. Кроме того, уровни ив обычном приложении тоже недоступны. Вы можете пользоваться ими, только если пишете драйвер устройства, работающий в режиме ядра. И еще одно:
уровень приоритета потока в процессе с классом real time не может опускаться ниже, а потока в процессе с любым другим классом — подниматься выше Концепция класса приоритета вводит некоторых в заблуждение. Они делают отсюда вывод, будто процессы участвуют в распределении процессорного времени. Так вот, процессы никогда не получают процессорное время — оно выделяется лишь потокам. Класс приоритета процесса — сугубо абстрактная концепция, введенная Microsoft с единственной целью скрыть от разработчика внутреннее устройство планировщика.
В общем случае поток с высоким уровнем приоритета должен быть активен как можно меньше времени. При появлении у него какой либо работы он тут же получает процессорное время. Выполнив минимальное количество команд,
он должен снова вернуться в ждущий режим. С другой стороны, поток с низким уровнем приоритета может оставаться активными занимать процессор довольно долго. Следуя этим правилам, Вы сохраните должную отзывчивость операционной системы на действия пользователя.
Программирование приоритетов
Так как же процесс получает класс приоритета Очень просто. Вызывая
CreateProcess,
Вы можете указать в ее параметре
fdwCreate нужный класс приоритета. Идентификаторы этих классов приведены в следующей таблице.

172
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
Класс приоритета
Идентификатор
Real time
REALTIME_PRIORITY_CLASS
High
HIGH_PRIORITY_CLASS
Above normal
ABOVE_NORMAL_PRIORITY_CLASS
Normal
NORMAL_PRIORITY_CLASS
Below Вам может показаться странным, что, создавая дочерний процесс, родительский сам устанавливает ему класс приоритета. За примером далеко ходить не надо — возьмем все тот же Explorer. При запуске из него какого нибудь приложения новый процесс создается с обычным приоритетом. Но Explorer ведь не знает, что делает этот процесс и как часто его потокам надо выделять процессорное время. Поэтому в системе предусмотрена возможность изменения класса приоритета самим выполняемым процессом — вызовом функции
SetPriorityClass:
BOOL SetPriorityClass(
HANDLE hProcess,
DWORD Эта функция меняет класс приоритета процесса, определяемого описателем
hPro
cess, в соответствии со значением параметра fdwPriority. Последний должен содержать одно из значений, указанных в таблице выше. Поскольку
SetPriorityClass принимает описатель процесса, Вы можете изменить приоритет любого процесса, выполняемого в системе, — если его описатель известен и у Вас есть соответствующие права дос тупа.
Обычно процесс пытается изменить свой класс приоритета. Вот как процесс может сам себе установить класс приоритета idle:
BOOL SetPriorityClass(GetCurrentProcess(), Парная ей функция
GetPriorityClass позволяет узнать класс приоритета любого процесса GetPriorityClass(HANDLE Она возвращает, как Вы догадываетесь, один из ранее перечисленных флагов.
При запуске из оболочки командного процессора начальный приоритет программы тоже обычный. Однако, запуская ее командой Start, можно указать ключ, определяющий начальный приоритет. Так, следующая команда, введенная в оболочке командного процессора, заставит систему запустить приложение Calculator и присвоить ему приоритет idle:
C:\>START /LOW Команда Start допускает также ключи /BELOWNORMAL, /NORMAL, /ABOVENORMAL,
/HIGH и /REALTIME, позволяющие начать выполнение программы с соответствующим классом приоритета. Разумеется, после запуска программа может вызвать
SetPriority
Class и установить себе другой класс приоритета.
В Windows 98 команда Start не поддерживает ни один из этих ключей. Из оболочки командного процессора Windows 98 процессы всегда запускаются с классом приоритета normal.

173
Г ЛАВА 7
Планирование потоков, приоритет и привязка к процессорам Manager в Windows 2000 дает возможность изменять класс приоритета процесса. На рисунке ниже показана вкладка Processes в окне Task Manager со списком выполняемых на данный момент процессов. В колонке Base Pri сообщается класс приоритета каждого процесса. Вы можете изменить его, выбрав процесс и указав другой класс в подменю Set Priority контекстного меню.
Только что созданный поток получает относительный приоритет normal. Почему
CreateThread не позволяет задать относительный приоритет — для меня таки остается загадкой. Такая операция осуществляется вызовом функции SetThreadPriority(
HANDLE hThread,
int Разумеется, параметр
hThread указывает на поток, чей приоритет Вы хотите изменить, а через
nPriority передается один из идентификаторов (см. таблицу ниже).
Относительный приоритет потока
Идентификатор
Time critical
THREAD_PRIORITY_TIME_CRITICAL
Highest
THREAD_PRIORITY_HIGHEST
Above normal
THREAD_PRIORITY_ABOVE_NORMAL
Normal
THREAD_PRIORITY_NORMAL
Below Функция
GetThreadPriority, парная SetThreadPriority, позволяет узнать относительный приоритет потока GetThreadPriority(HANDLE Она возвращает один из идентификаторов, показанных в таблице выше.

174
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
Чтобы создать поток с относительным приоритетом idle, сделайте, например, так dwThreadID;
HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, NULL,
CREATE_SUSPENDED, &dwThreadID);
SetThreadPriority(hThread, Заметьте, что
CreateThread всегда создает поток с относительным приоритетом normal. Чтобы присвоить потоку относительный приоритет idle, создайте приостановленный поток, передав в
CreateThread флага потом вызовите и установите нужный приоритет. Далее можно вызвать Resume
Thread, и поток будет включен в число планируемых. Сказать заранее, когда поток получит процессорное время, нельзя, но планировщик уже учитывает его новый приоритет. Выполнив эти операции, Вы можете закрыть описатель потока, чтобы соответствующий объект ядра был уничтожен по завершении данного потока.
Ни одна Windows функция не возвращает уровень приоритета потока. Такая ситуация создана преднамеренно. Вспомните, что Microsoft может в любой момент изменить алгоритм распределения процессорного времени. Поэтому при разработке приложений не стоит опираться на какие то нюансы этого алгоритма. Используйте классы приоритетов процессов и относительные приоритеты потоков, и Ваши приложения будут нормально работать как в нынешних, таки в следующих версиях Windows.
Динамическое изменение уровня приоритета потока
Уровень приоритета, получаемый комбинацией относительного приоритета потока и класса приоритета процесса, которому принадлежит данный поток, называют
ба
зовым уровнем приоритета потока. Иногда система изменяет уровень приоритета потока. Обычно это происходит в ответ на некоторые события, связанные с вводом выводом (например, на появление оконных сообщений или чтение с диска).
Так, поток с относительным приоритетом normal, выполняемый в процессе с классом приоритета high, имеет базовый приоритет 13. Если пользователь нажимает какую нибудь клавишу, система помещает в очередь потока сообщение А поскольку в очереди потока появилось сообщение, поток становится планируемым.
При этом драйвер клавиатуры может заставить систему временно поднять уровень приоритета потока с 13 до 15 (действительное значение может отличаться в ту или другую сторону).
Процессор исполняет поток в течение отведенного отрезка времени, а по его истечении система снижает приоритет потока надо уровня 14. Далее потоку вновь выделяется квант процессорного времени, по окончании которого система опять снижает уровень приоритета потока на 1. И теперь приоритет потока снова соответствует его базовому уровню.
Текущий уровень приоритета не может быть ниже базового. Кроме того, драйвер устройства, разбудивший поток, сам устанавливает величину повышения приоритета. И опять жене документирует, насколько повышаются эти значения конкретными драйверами. Таким образом, она получает возможность тонко настраивать динамическое изменение приоритетов потоков в операционной системе, чтобы та максимально быстро реагировала на действия пользователя.

175
Г ЛАВА 7
Планирование потоков, приоритет и привязка к процессорам
Система повышает приоритет только тех потоков, базовый уровень которых находится в пределах 1–15. Именно поэтому данный диапазон называется областью динамического приоритета (dynamic priority range). Система не допускает динамического повышения приоритета потока до уровней реального времени (более Поскольку потоки с такими уровнями обслуживают системные функции, это ограничение не дает приложению нарушить работу операционной системы. И, кстати, система никогда не меняет приоритет потоков с уровнями реального времени (от 16 до Некоторые разработчики жаловались, что динамическое изменение приоритета системой отрицательно сказывается на производительности их приложений, и поэтому добавила две функции, позволяющие отключать этот механизм SetProcessPriorityBoost(
HANDLE hProcess,
BOOL DisablePriorityBoost);
BOOL SetThreadPriorityBoost(
HANDLE hThread,
BOOL DisablePriorityBoost);
SetProcessPriorityBoost заставляет систему включить или отключить изменение приоритетов всех потоков в указанном процессе, а
SetThreadPriorityBoost действует применительно к отдельным потокам. Эти функции имеют свои аналоги, позволяющие определять, разрешено или запрещено изменение приоритетов GetProcessPriorityBoost(
HANDLE hProcess,
PBOOL pDisablePriorityBoost);
BOOL GetThreadPriorityBoost(
HANDLE hThread,
PBOOL Каждой из этих двух функций Вы передаете описатель нужного процесса или потока и адрес переменной типа BOOL, в которой и возвращается результат.
В Windows 98 эти четыре функции определены, ноне реализованы, и при вызове любой из них возвращается FALSE. Последующий вызов
GetLastError дает
ERROR_CALL_NOT_IMPLEMENTED.
Есть еще одна ситуация, в которой система динамически повышает приоритет потока. Представьте, что поток с приоритетом 4 готов к выполнению, ноне может получить доступ к процессору из за того, что его постоянно занимают потоки с приоритетом. Это типичный случай голодания потока с более низким приоритетом.
Обнаружив такой поток, не выполняемый на протяжении уже трех или четырех секунд, система поднимает его приоритет дои выделяет ему двойную порцию времени. По его истечении потоку немедленно возвращается его базовый приоритет.



Поделитесь с Вашими друзьями:
1   ...   16   17   18   19   20   21   22   23   ...   68


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал