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

псевдоописатель,

GetCurrentProcess(), // псевдоописатель процесса GetCurrentProcess(), // описатель процесса, к которому относится новый, настоящий описатель;

&hProcess, // дает новый, настоящий описатель идентифицирующий процесс,

0, // игнорируется из-за DUPLICATE_SAME_ACCESS, FALSE, // новый описатель процесса ненаследуемый, DUPLICATE_SAME_ACCESS); // новому описателю процесса присваиваются

// те же атрибуты защиты, что и псевдоописателю

ГЛАВА 7 Планирование потоков, приоритет и привязка к процессорам

Операционная система с вытесняющей многозадачностью должна использовать тот или иной алгоритм, позволяющий ей распределять процессорное время между пото ками Здесь мы рассмотрим алгоритмы, применяемые в Windows 98 и Windows 2000. Б главе 6 мы уже обсудили структуру CONTEXT, поддерживаемую в объекте ядра "поток", и выяснили, что она отражает состояние регистров процессора на момент последнего выполнения потока процессором Каждые 20 мс (или около того) Windows просматривает все существующие объекты ядра "поток" и отмечает те из них, кото рые могут получать процессорное время. Далее она выбирает один из таких объек тов и загружает в регистры процессора значения из его контекста Эта операция на зывается переключением контекста (context switching) По каждому потоку Windows ведет учет того, сколько раз он подключался к процессору. Этот показатель сообща ют специальные утилиты вроде Microsoft Spy++ Например, на иллюстрации ниже показан список свойов одного из потоков. Обратите внимание, что этот поток под ключался к процессору 37379 раз

Поток выполняет код и манипулирует данными в адресном пространстве своего процесса Примерно через 20 мс Windows сохранит значения регистров процессора в контексте потока и приостановит сго выполнение. Далее система просмотрит ос тальные объекты ядра "поток", подлежащие выполнению, выберет один из них, заг рузит его контскст в регистры процессора, и все повторится Этот цикл операций — выбор потока, загрузка его

контекста, выполнение и сохранение контекста — начи нается с момента запуска системы и продолжается до cc выключения.

Таков вкратце механизм планирования работы множества потоков. Детали мы обсудим позже, но главное я уже показал Все очень просто, да? Windows потому и называется системой с вытесняющей многозадачностью, что в любой момент может приостановить любой поток и вместо него запустить другой. Как Вы еще увидите, этим механизмом можно управлять, правда, крайне ограниченно. Всегда помните: Вы не в состоянии гарантировать, что Ваш поток будет выполняться непрерывно, что ника кой другой поток не получи'1 доступ к процессору и т д.

NOTE:

Меня часто спрашивают, как сделать так, чтобы поток гарантированно запус кался в течение определенного времени после какого-нибудь события — на пример, не позднее чем через миллисекунду после приема данных с последо вательного порта? Ответ прост: никак. Такие требования можно предъявлять к операционным системам реального времени, но Windows к ним не относит ся. Лишь операционная система реального времени имеет полное представле ние о характеристиках аппаратных средств, на которых она работает (об ин тервалах запаздывания контроллеров жестких дисков, клавиатуры и т. д.). А создавая Windows, Microsoft ставила другую цель обеспечить поддержку мак симально широкого спектра оборудования — различных процессоров, диско вых устройств, сетей и др. Короче говоря, Windows не является операционной системой реального времени.

Хочу особо подчеркнуть, что система планирует выполнение только тех потоков, которые могут получать процессорное время, но большинство потоков в системе к таковым не относится. Так, у некоторых объектов-потоков значение счетчика просто ев (suspend count) больше 0, а значит, соответствующие потоки приостановлены и не получают процессорное время. Вы можете создать приостановленный поток вызовом CreateProcess или CreateThread с флагом CREATESUSPENDED (В следующем разделе я расскажу и о таких функциях, как SuspendThread и ResumeThread.)

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

Приостановка и возобновление потоков

В объекте ядра "поток" имеется переменная — счетчик числа простоев данного по тока При вызове CreateProcess или CreateThread он инициализируется значением, рав ным 1, которое запрещает системе выделять новому потоку процессорное время. Та кая схема весьма разумна: сразу после создания поток не готов к выполнению, ему нужно время для инициализации.

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

Создав поток в приостановленном состоянии, Выможете настроить некоторые его свойства (например, приоритет, о котором мы поговорим позже). Закончив настройку, Вы должны разрешить выполнение потока. Для этого вызовите ResumeThread и пере дайте описатель потока, возвращенный функцией CreateThread (описатель можно взять и из структуры, на которую указывает параметр ppiProcInfo, передаваемый в CreateProcess).

DWORD ResumeThread(HANDLE hThread);

Если вызов ResumeThread прошел успешно, она возвращает предыдущее значение счетчика простоев данного потока; в ином случае — 0xFFFFFFFF.

Выполнение отдельного потока можно приостанавливать несколько раз. Если поток приостановлен 3 раза, то и возобновлен он должен быть тоже 3 раза — лишь тогда система выделит ему процессорное время. Выполнение потока можно приос тановить не только при его создании с флагом CREATE_SUSPENDED, но и вызовом SuspendThread.

DWORD RuspendThread(HANDLE hThread);

Любой поток может вызвать эту функцию и приостановить выполнение другого потока (конечно, если его описатель известен). Хоть об этом нигде и не говорится (но я все равно скажу!), приостановить свое выполнение поток способен сам, а во зобновить себя без посторонней помощи — нет. Как и ResumeThread, функция Sus pendThread возвращает предыдущее значение счетчика простоев данного потока. Поток можно приостанавливать не более чем MAXIMUM_SUSPEND_COUNT раз (в файле WinNT.h это значение определено как 127). Обратите внимание, что Suspend Thread в режиме ядра работает асинхронно, но в пользовательском режиме не выпол няется, пока потокостается в приостановленном состоянии.

Создавая реальное приложение, будьте осторожны с вызовами SuspendThread, так как нельзя заранее сказать, чем будет заниматься его поток в момент приостановки. Например, он пытается выделить память из кучи и поэтому заблокировал к ней дос туп. Тогда другим потокам, которым тоже нужна динамическая память, придется ждать его возобновления. SuspendThread безопасна только в том случае, когда Вы точно знаете, что делает (или может делать) поток, и предусматриваете все меры для исклю чения вероятных проблем и взаимной блокировки потоков. (О взаимной блокировке и других проблемах синхронизации потоков я расскажу в главах 8, 9 и 10.)

Приостановка и возобновление процессов

В Windows понятия "приостановка" и "возобновление" неприменимы к процессам, так как они не участвуют в распределении процессорного времени. Однако меня не рая спрашивали, как одним махом приостановить все потоки определенного процесса. Это можно сделать из другого процесса, причем он должен быть отладчиком и, в ча стности,

вызывать функции вроде WaitForDebugEvent и ContinueDebugEvent.

Других способов приостановки всех потоков процесса в Windows нет: програм ма, выполняющая такую операцию, может "потерять" новые потоки. Система должна как-то приостанавливать в этот период не только все существующие, но и вновь со здаваемые потоки. Microsoft предпочла встроить эту функциональность в системный механизм отладки.

Вам, конечно, не удастся написать идеальную функцию SuspendProcess, но вполне по силам добиться ec удовлетворительной работы во многих ситуациях. Вот мой ва риант функции SuspendProcess.

VOID SuspendProcess(DWORD dwProcessID, BOOL tSuspend)

{

// получаем список потоков в системе

HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessID),

if (hSnapshot != INVALID_HANDLE_VALUE) {

// просматриваем список потоков

THREADENTRY32 te = { sizeof(te) };

BOOL fOk = Thread32First(hSnapshot, &te);

for (, fOk, fOk = Thread32Next(hSnapshot, &te))

{

//относится ли данный поток к нужному процессу if (te.th320wnerProcessID == dwProcessID)

{

//пытаемся получить описатель потока по его идентификатору

HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te th32ThreadID);

if (hThread != NULL)

{

// приоcтанавливаем или возобновляем поток if (fSuspend)

SuspendTh read(hThread); else ResumeThread(hThread);

}

CloseHandle(hThread);

}

}

CloseHandle(hSnapsnot);

}

}

Для перечисления списка потоков я использую ToolHelp функции (они рассмат ривались в главе 4). Определив потоки нужною процесса, я вызываю OpenThread.

HANDLE OpenThread( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadID);

Это новая функция, которая появилась в Windows 2000 Она находит объект ядра "поток" по идентификатору, указанному в dwTbreadJD, увеличивает его счетчик поль зователей на 1 и возвращает описатель объекта Получив описатель, я могу передать его в