Управление процессами и потоками в ОС Windows
В ОС Windows 2000 для реализации механизма многозадачности используется два основных понятия: процесс (Process) и поток (Thread). Первый из них реализует концепцию владения ресурсами, а второй - концепцию планирования центрального процессора.
Процесс в ОС Windows 2000 является контейнером для ресурсов и состоит из двух компонентов:
- объекта ядра, через который операционная система управляет процессом. Там же хранится статистическая информация о процессе.
- адресного пространства, в котором содержится код и данные всех EXE- и DLL модулей. Именно в нем располагаются области памяти, динамически распределяемые для стеков потоков и других нужд. Каждому процессу выделяется 4-гигабайтное адресное пространство, в котором пользователь занимает нижние 2 Гбайта, а ОС занимает остальную часть. Таким образом, ОС присутствует в адресном пространстве каждого процесса, но защищена от него с помощью аппаратного блока управления памятью MMU (Meneger Memory Unit).
табл. 13 - Варианты индивидуальных заданий
№ вар. | Компонент VCL для управления приоритетами | Значения базовых и относительных приоритетов для оценки времени выполнения потоков | |||
Название | Страница палитры компонентов | Изображение | Базовые приоритеты процесса | Относительные приоритеты потоков | |
TrackBar | Win32 | BELOW_NORMAL NORMAL | LOWEST NORMAL | ||
ComboBox | Standard | NORMAL ABOVE_NORMAL | NORMAL ABOVE_NORMAL | ||
RadioButton | Standard | BELOW_NORMAL NORMAL | LOWEST NORMAL | ||
SpinEdit | Samples | ABOVE_NORMAL HIGH | LOWEST BELOW_NORMAL | ||
TrackBar | Win32 | NORMAL ABOVE_NORMAL | BELOW_NORMAL ABOVE_NORMAL | ||
ComboBox | Standard | ABOVE_NORMAL HIGH | BELOW_NORMAL NORMAL | ||
SpinEdit | Samples | BELOW_NORMAL NORMAL | ABOVE_NORMAL HIGHEST |
табл. 14 - Оценка времени выполнения процессов и потоков
Запущенных процессов | ID процесса | Класс приоритета процесса | Время выполнения процесса | ID потока | Относительный приоритет потока | Время выполнения потока | Кол-во вычислений |
BELOW_ NORMAL | |||||||
NORMAL | |||||||
Процессы по своей сути инертны. Чтобы процесс что-нибудь выполнил, в нем нужно создать поток. Поток процесса является выполняемой единицей, которая располагается в адресном пространстве процесса и использует ресурсы, выделенные процессу. В принципе, один процесс может владеть несколькими потоками, и тогда они попеременно исполняют код в адресном пространстве процесса. При создании процесса первый (точнее, первичный) поток создается системой автоматически.
Создаются процессы и потоки при помощи функций Win32 API.
Для создания процесса используется функция CreateProcess, назначение параметров которой приведено в табл. 15. При вызове данной функции система создает объект ядра "процесс", создает для нового процесса виртуальное адресное пространство, загружает в него код и данные как для исполняемого файла, так и для любых DLL (если таковые требуются). Как было указано выше этот объект - компактная структура данных, через которую операционная система управляет процессом. Далее система формирует объект ядра "поток" для первичного потока нового процесса. Как и в первом случае, объект ядра "поток" — это компактная структура данных, через которую система управляет потоком. Если системе удастся создать новый процесс и его первичный поток, CreateProcess возвращает значение TRUE. В тело созданного объекта-процесса входят следующие поля:
- идентификатор процесса - уникальное значение, которое идентифицирует процесс в рамках операционной системы;
- маркер доступа, содержащий информацию о безопасности и защите;
- класс приоритета процесса - основа для исполнительного приоритета потоков процесса;
- процессорная совместимость - набор процессоров, на которых могут выполняться потоки процесса;
- предельные значения квот - максимальное количество системной памяти, дискового пространства, предназначенного для выгрузки страниц, процессорного времени - которые могут быть использованы процессами пользователя;
- время создания, завершения, работы процесса в режимах ядра и пользователя. Время работы процессора в режимах ядра и пользователя - это общее количество времени выполнения для всех потоков процесса.
Подобно процессу поток реализован в форме объекта и состоит из двух компонентов:
- объекта ядра, через который операционная система управляет потоком. Там же хранится статистическая информация о потоке;
- стека потока, который содержит параметры всех функций и локальные переменные, необходимые потоку для выполнения кода.
табл. 15 - Параметры функции CreateProcess
Название параметра | Назначение параметра |
lpApplicationName | Указатель на имя исполняемого файла |
lpCommandLine | Указатель на командную строку |
lpProcessAttributes | Указатель на дескриптор защиты процесса (атрибуты доступа) |
lpThreadAttributes | Указатель на дескриптор защиты первичного потока |
bInheritHandles | Бит, управляющий наследованием дескрипторов |
dwCreationFlags | Комбинация битовых флагов, определяющая способы и режимы создания процесса (способ создания, класс приоритета процесса) |
lpEnvironment | Указатель на переменные окружения процесса |
lpCurrentDirectory | Указатель на имя текущего рабочего каталога процесса |
lpStartupInfo | Указатель на структуру, описывающую параметры окна на экране |
lpProcessInformation | Указатель на структуру, в которой сохранена информация о порожденном процессе и первичном потоке: значения их свойств Handle и идентификаторов. |
табл. 16 Параметры функции CreateThread
Название параметра | Назначение параметра |
lpThreadAttributes | Указатель на дескриптор защиты |
dwStackSize | Начальный размер стека |
lpStartAddress | Адрес точки входа функции потока |
lpParameter | Параметры, задаваемые пользователем |
dwCreationFlags | Поле, задающее начальное состояние потока (готовый или блокированный) |
lpThreadId | Указатель на идентификатор потока |
При создании процесса первый (точнее, первичный) поток создается системой автоматически. Далее этот поток может динамически порождать другие потоки, те в свою очередь - новые и т. д.
ОС выбирает для запуска и ставит на выполнение на центральном процессоре (ЦП) именно потоки, поэтому у каждого потока есть текущее состояние – готовый к запуску, работающий, блокированный и т.д. Для сохранения состояния регистров ЦП на время пока поток не выполняется, используется специальная информационная структура, называемая контекстом потока. Для смены работающего потока другим необходимо выполнить переключение их контекстов.
Как было сказано выше, при вызове функции CreateProcessсоздается процесс и его первичный поток. Если необходимо создать дополнительные потоки, нужно вызывать из первичного потока функцию CreateThread, назначение параметров которой приведено в табл. 16.
При вызове функции CreateThread происходит следующее:
- система создает объект ядра “поток”. Это не сам поток, а компактная структура данных, которая используется операционной системой для управления потоком и хранит статистическую информацию о потоке.
- система выделяет память под стек потока из адресного пространства процесса. Новый поток выполняется в контексте того же процесса, что и первичный поток. Поэтому он получает доступ ко всем дескрипторам объектов ядра, всей памяти и стекам всех потоков в процессе. За счет этого потоки в рамках одного процесса могут легко взаимодействовать друг с другом;
- создается структура контекста для потока;
- инициализируются регистры указателя стека и указателя команд.
Если системе удастся создать новый поток, то функция CreateThread возвращает Handle созданного потока, в противном случае возвращается значение NULL.
В тело созданного объекта-процесса входят следующие поля:
- идентификатор потока;
- контекст потока;
- динамический приоритет - значение приоритета нити в данный момент;
- базовый приоритет - нижний предел динамического приоритета потока;
- процессорная совместимость потока - перечень типов процессоров, на которых может выполняться поток;
- время создания, завершения, работы потока в режимах ядра и пользователя;
- стека потока, который содержит параметры всех функций и локальные переменные, необходимые потоку для выполнения кода;
- счетчик приостановок - текущее количество приостановок выполнения потока.
Рассмотрим механизм планирования работы потоков. ОС Windows 2000 называют ОС с вытесняющей многозадачностью, т.е. когда планирование потоков целиком сосредоточено в ОС, и она сама принимает решение в какой момент приостановить выполнение одного потока и запустить другой.
Каждые 20мс ОС просматривает все существующие объекты ядра "поток" и отмечает те из них, которые могут получить процессорное время. Далее она выбирает один из таких объектов и загружает в регистры процессора значения из его контекста. Эта операция называется переключениемконтекста(context switching). По каждому потоку Windows ведет учет того, сколько раз он подключался к процессору.
Причин, по которым осуществляется замена действующего потока другим, может быть несколько:
- поток завершил свою работу;
- поток исчерпал выделенный ему квант времени;
- поток заблокирован и находится в состоянии ожидания;
- в очереди появился поток с более высоким приоритетом.
Таким образом, в ОС используется алгоритм планирования, основанный на квантовании и приоритетах. Windows 2000 поддерживает 32 уровня приоритетов, разделенных на есть классов (
табл. 17)- реального времени, высокого, выше нормы, нормального, ниже нормы и простаивающего. Самый распространенный класс приоритета – нормальный - его использует 99% приложений.
Класс приоритета присваивается процессу при его создании системой по умолчанию или запускающей программой. Класс приоритета процесса можно изменить, используя функцию Win32 SetPriorityClass.
В тоже время, Windows 2000 поддерживает 7 относительных уровней приоритета потока (табл. 18): критичного ко времени, высшего, выше обычного, нормального, ниже обычного, низшего и простаивающего.
Относительный приоритет присваивается потоку при его создании системой по умолчанию или запускающей программой. Относительный приоритет потока можно изменить, используя функцию Win32 SetThreadPriority.
На основании класса приоритета процесса и относительного приоритета потока система присваивает потоку текущий (исполнительный) приоритет, называемый исполнительным приоритетом планирования. Возможные значения исполнительных приоритетов приведены в табл. 19.
табл. 17 Классы приоритетов процесса
Константа Win32 | Значение | Описание |
IDLE_ PRIORITY_CLASS | 0x0040 | Потоки в этом процессе выполняются, когда система не занята другой работой. Этот класс приоритета обычно используется для утилит, работающих в фоновом режиме, экранных заставок и приложений, собирающих статистическую информацию |
BELOW_NORMAL_ PRIORITY_CLASS | 0x4000 | Класс приоритета, промежуточный между normal и idle. |
NORMAL_ PRIORITY_CLASS | 0x0020 | Потоки в этом процессе не предъявляют особых требований к выделению им процессорного времени. |
ABOVE_NORMAL_ PRIORITY_CLASS | 0x8000 | Класс приоритета, промежуточный между normal и high. |
HIGH_PRIORITY_ CLASS | 0x0080 | Потоки в этом процессе тоже должны немедленно реагировать на события, обеспечивая выполнение критических по времени задач. Этот класс присвоен, например, Task Manager, что дает возможность пользователю закрывать неконтролируемые процессы. |
REALTIME_PRIORITY_ CLASS | 0x0100 | Потоки в этом процессе обязаны немедленно реагировать на события, обеспечивая выполнение критических по времени задач. Такие потоки вытесняют даже компоненты операционной системы. |
табл. 18 Относительные приоритеты потоков
Константа Win32 | Значение | Описание |
THREAD_PRIORITY_ IDLE | -15* | Поток выполняется с приоритетом 16 в классе real-time и с приоритетом 1 в других классах. |
THREAD_PRIORITY_ LOWEST | -2 | Поток выполняется с приоритетом на два уровня ниже обычного для данного класса. |
THREAD_PRIORITY_ BELOW_NORMAL | -1 | Поток выполняется с приоритетом на один уровень ниже обычного для данного класса. |
THREAD_PRIORITY_ NORMAL | Поток выполняется с обычным приоритетом процесса для данного класса. | |
THREAD_PRIORITY_ ABOVE_NORMAL | Поток выполняется с приоритетом на один уровень выше обычного для данного класса. | |
THREAD_PRIORITY_ HIGHEST | Поток выполняется с приоритетом на два уровня выше обычною для данного класса. | |
THREAD_PRIORITY_TIME_CRITICAL | 15* | Поток выполняется с приоритетом 31 в классе real-time и с приоритетом 15 в других классах. |
табл. 19 Исполнительные приоритеты потоков
Относительный приоритет потока - THREAD_PRIORITY | Класс приоритета процесса - PRIORITY_CLASS | |||||
IDLE | BELOW_ NORMAL | NORMAL | ABOVE_ NORMAL | HIGH | REALTIME | |
IDLE | ||||||
LOWEST | ||||||
BELOW_NORMAL | ||||||
NORMAL | ||||||
ABOVE_NORMAL | ||||||
HIGHEST | ||||||
TIME_CRITICAL |
Как отмечалось выше, объекты ядра процесса и потока хранят информацию о времени создания, уничтожения, работы в режимах ядра и пользователя. Для получения этой информации используются соответственно функции Win32 API GetProcessTimes и GetThreadTimes.
Возвращаемые параметры перечисленных функций представлены в формате FileTime, который представляет собой 64-разрядное значение – количество 100 наносекундных интервалов, прошедших с 1 января 1601 года. Такое представление неудобно для использования в программах и требует дополнительного преобразования, например, в формат SystemTime(функция FileTimeToSystemTime). Формат SystemTime представляет собой запись, состоящую из полей-значений текущего времени: года, месяца, числа месяца, дня недели, часов, минут и секунд.
Выполнение работы
К п.1. При помощи программы Проводник необходимо создать рабочую папку проекта в своем каталоге, например G:\DSV\SPO\3-1\MYFAMILY\LAB1, и затем запустить Delphi. После загрузки программы на экране появится оболочка среды с заготовкой приложения Windows. Затем следует сохранить проект в ранее созданной папке проекта. Для этого нужно выбрать пункт меню File½Save Project As…, и в появившемся диалоговом окне установить путь к файлам модулей проекта (расширение *.pas) и основному файлу проекта (расширение *.dpr).
После этого можно приступить к разработке приложения, внешний вид основного окна которого приведен на рис. 5.
рис. 5 Внешний вид основного окна приложения
К п.2. Задайте подходящее значение для свойства Caption формы Form1, а свойство BoderStyle установите в значение bsDialog (диалоговое окно). После этого разместите на форме компонент Panel1 (панель), задайте свойству Align значение alTop и удалите значение свойства Caption.
Затем последовательно разместите на панели указанные ниже компоненты, и после размещения каждого из них задавайте свойству Align компонента значение alTop:
- Label1 (Caption = Состояние процесса);
- StringGrid1 (ColCount = 2, Enabled = False, FixedCols = 0, FixedRows = 0, RowCount = 4);
- Label2 (Caption = Приоритет процесса);
- TrackBar1(Max = 4, Position = 2).
МеткиLabel1 и Label2 отображают назначение соответственно компонентов StringGrid1 (таблица) и TrackBar1(ползунок).
Первый из них используется для отображения статистической текстовой информации о состоянии процесса, поэтому, для вывода названий параметров процесса в левой колонке таблицы StringGrid1 создайте обработчик события OnCreate формы TForm1, который должен содержать следующие операторы:
procedure TForm1.FormCreate(Sender: TObject);
begin
StringGrid1.Cells[0,0]:='Идентификатор процесса';
StringGrid1.Cells[0,1]:='Время создания процесса';
StringGrid1.Cells[0,2]:='Время работы процесса';
StringGrid1.Cells[0,3]:='Класс приоритета процесса';
…………
end;
Второй компонент – ползунок – используется для задания или изменения класса приоритета процесса (
табл. 17). При выполнении лабораторной работы не рекомендуется использовать класс приоритета для процессов реального времени - REALTIME_PRIORITY_CLASS, поэтому TrackBar1 должен иметь пять фиксированных позиций. По умолчанию Delphi присваивает процессу класс приоритета NORMAL_PRIORITY_CLASS, поэтому первоначально ползунок должен быть установлен на второй позиции.
Для изменения класса приоритета процесса создайте обработчик события OnChange для TrackBar1, который в зависимости от положения ползунка будет изменять класс приоритета процесса:
procedure TForm1.TrackBar1Change(Sender: TObject);
var ClassPriority: integer;
begin
case TrackBar1.Position of
0: ClassPriority:=IDLE_PRIORITY_CLASS;
// 1: ClassPriority:=BELOW_NORMAL_PRIORITY_CLASS;
1: ClassPriority:=$4000;
2: ClassPriority:=NORMAL_PRIORITY_CLASS;
// 3: ClassPriority:=ABOVE_NORMAL_PRIORITY_CLASS;
3: ClassPriority:=$8000;
4: ClassPriority:=HIGH_PRIORITY_CLASS;
end;
SetPriorityClass(GetCurrentProcess,ClassPriority);
end;
В Delphi 6.0 не учтены два новых класса приоритетов процессов добавленных в Windows 2000: BELOW_NORMAL_PRIORITY_CLASS и ABOVE_NORMAL_PRIORITY_CLASS, поэтому для задания приоритета необходимо использовать конкретные значения констант. Для получения свойства Handle текущего процесса и установки его класса приоритета используются соответственно функции Win32 GetCurrentProcess и SetPriorityClass, более подробную информацию о них можно получить в разделе Windows SDK справочной системы Delphi.
Затем разместите на текущей панели кнопку Button1 (Caption = “Закрыть”) и создайте для нее обработчик события OnClick для обеспечения возможности выхода из программы:
procedure TForm1.Button1Click(Sender: TObject);
begin
Close;
end;
Для мониторинга текущего состояния процесса и порожденных им потоков разместите на панели компонент Timer co страницы System. Компонент Timer является невизуальным, то есть он не виден на форме во время выполнения программы. Компонент таймер работает следующим образом: через заданное число миллисекунд, которое определяется свойством Interval, генерируется событие OnTimer. В обработчике данного события нужно поместить код, который будет выполняться с заданной периодичностью. При выполнении лабораторной работы рекомендуется установить интервал таймера в 1 сек. (1000 мс).
procedure TForm1.Timer1Timer(Sender: TObject);
var STime: TSystemTime;
CrTime,ExTime,KrTime,UsTime: TFileTime;
begin
StringGrid1.Cells[1,0]:=IntToStr(GetCurrentProcessID);
StringGrid1.Cells[1,3]:=IntToStr(GetPriorityClass(GetCurrentProcess));
if GetProcessTimes(GetCurrentProcess,CrTime,ExTime,KrTime,UsTime) then begin
if FileTimeToSystemTime(CrTime,STime) then
StringGrid1.Cells[1,1]:=DateTimeToStr(SystemTimeToDateTime(STime));
if FileTimeToSystemTime(UsTime,STime) then
StringGrid1.Cells[1,2]:=TimeToStr(SystemTimeToDateTime(STime));
end;
……………
end;
Для получения идентификатора и класса приоритета процесса используются соответственно функции Win32 GetCurrentProcessID, GetPriorityClass. Для получения информации о времени создания, завершения, времени работы в режимах ядра и пользователя используется функция Win32 GetProcessTimes, которая возвращает перечисленные значения в формате времени TFileTime. Для отображения времени используется три промежуточных преобразования форматов времени:
- TFileTime в формат TSystemTime– функция FileTimeToSystemTime;
- TSystemTime в формат TDateTime – функция SystemTimeToDateTime;
- TDateTime при помощи функций DateTimeToStr и TimeToStr преобразуем соответственно в строки символов времени-даты создания и времени работы процесса в пользовательском режиме
TDateTime является внутренним форматом представления времени в Delphi. Тип TDateTime хранится в вещественной переменной типа Double, целая часть которой соответствует количеству дней, прошедших с 30 декабря 1899 года, а дробная – части текущего дня (6.00 ® 0.25, 12.00 ® 0.5, 18.00 ® 0.75).
При выполнении работы время завершения работы и время работы процесса в режиме ядра не являются информативными и поэтому не используются.
К п.2. Для добавления потока в приложение выберите в основном меню пункт File/New/Other/Thread Object. В появившемся диалоговом окне задайте имя класса добавляемого потока, например TMyThread. После этого появится заготовка модуля соответствующего потока.
unit Unit1;
interface
uses Classes;
type
TMyThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
procedure TMyThread.Execute;
begin
…………
end;
end.
Класс TThread, от которого порожден поток, является объектно-ориентированной надстройкой Delphi над потоками, создаваемыми функциями Win32 API. Он освобождает пользователя от необходимости прямого использования функций Windows API для управления потоками, сохраняя при этом возможность такого управления через свойство Handle. Метод Execute должен содержать код, который будет выполняться потоком во время его работы.
Для подсчета числа суммирований, выполняемых потоком, следует использовать целочисленную переменную Counter, для этого в описание класса TMyThread следует добавить раздел public и описать в нем поле Counter типа Integer.
type
TMyThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
public
Counter: Integer;
end;
Метод Execute, который выполняет вычисления, следует реализовать примерно следующим образом:
procedure TMyThread.Execute;
var i,Total: integer;
begin
while not Terminated do begin
Total:=0;
for i:=1 to 10 do Inc(Total,Random(MaxInt div 10));
Inc(Counter);
end;
Поскольку обычно поток выполняет код, расположенный в методе Execute и завершает свою работу, код внутри метода следует зациклить. Для этого удобно использовать свойство Terminated потока. Свойство Terminated имеет значение True в том случае, когда по каким-то причинам следует завершить работу потока, например из основного потока приложения.
После определения кода, который должен выполнять поток его следует сохранить в выделенной для проекта папке, предварительно определив название модуля, например Unit1. Затем подключить модуль, в котором определен поток, к проекту. Для этого в строке uses раздела interface основного модуля приложения следует написать название модуля, в котором определен поток, например:
Uses …., Unit1;
После этого в раздел описания переменных (var) основного модуля необходимо добавить описание двух переменных того же типа, что и поток:
Var Thread1,Thread2: TMyThread;
Теперь можно приступить к размещению компонентов на основной форме проекта для исследования работы потоков.
Вначале разместите на форме компонент Panel2 (панель), присвойте свойству Align значение alLeft и удалите значение свойства Caption.
Затем последовательно разместите на панели указанные ниже компоненты, и после размещения каждого из них задавайте свойству Align компонента значение alTop:
- Label3 (Caption = Состояние потока 1);
- StringGrid2 (ColCount = 2, Enabled = False, FixedCols = 0, FixedRows = 0, RowCount = 5);
- Label4 (Caption = Приоритет потока);
- TrackBar1(Max = 5, Position = 0).
После этого на ту же панель поместите следующие компоненты:
- CheckBox1 (Caption = Создать задержанным);
- Button2 (Caption = Создать поток);
- Button3 (Caption = Запустить поток);
- Button4 (Caption = Остановить поток);
- Button5 (Caption = Уничтожить поток);
МеткиLabel3 и Label4 отображают назначение соответственно компонентов StringGrid2 и TrackBar2).
Первый из них используется для отображения статистической текстовой информации о состоянии потока. Для вывода названий параметров потока в левой колонке таблицы StringGrid2 и обеспечения непротиворечивой работы кнопок Button1÷Button5, управляющих работой потока, добавьте к ранее созданному обработчику события OnCreate формы, следующий код:
procedure TForm1.FormCreate(Sender: TObject);
begin
……………
StringGrid2.Cells[0,0]:='Идентификатор потока
StringGrid2.Cells[0,1]:='Время создания потока';
StringGrid2.Cells[0,2]:='Время работы потока';
StringGrid2.Cells[0,3]:='Количество вычислений';
StringGrid2.Cells[0,4]:='Относительный приоритет';
Button3.Enabled:=false;
Button4.Enabled:=false;
Button5.Enabled:=false;
……………
end;
Компонент TrackBar2 используется для задания или изменения относительного приоритета потока (
табл. 17). Delphi использует свои константы относительных приоритетов потоков, соответствие между ними и константами Win32 приведено в табл. 20.
При выполнении лабораторной работы не рекомендуется использовать относительный приоритета потока критичного ко времени исполнения - THREAD_PRIORITY_TIME_CRITICAL, поэтому TrackBar2 должен иметь шесть фиксированных позиций.
табл. 20 Относительные приоритеты потока в Delphi и Win32
Константа Delphi | Константа Win32 | Значение |
tpIdle | THREAD_PRIORITY_IDLE | -15* |
tpLowest | THREAD_PRIORITY_LOWEST | -2 |
tpLower | THREAD_PRIORITY_BELOW_NORMAL | -1 |
tpNormal | THREAD_PRIORITY_NORMAL | |
tpHigher | THREAD_PRIORITY_ABOVE_NORMAL | |
tpHighest | THREAD_PRIORITY_HIGHEST | |
tpTimeCritical | THREAD_PRIORITY_TIME_CRITICAL | 15* |
При работе приложения предполагается создание двух потоков, поэтому для избежания избыточности кода, лучше создать одну процедуру установки приоритетов - общую для обоих потоков. Для этого в разделе public класса TForm1 необходимо описать метод SetThrPrior:
type
TForm1 = class(TForm)
…………
private
{ Private declarations }
public
procedure SetThrPrior(Thread: TMyThread; Pos: integer);
{ Public declarations }
end;
А затем в разделе implementation привести его реализацию:
procedure TForm1.SetThrPrior(Thread: TMyThread; Pos: integer);
var Priority: TThreadPriority;
begin
if Thread <> nil then begin
case Pos of
0: Priority:=tpIdle;
1: Priority:=tpLowest;
2: Priority:=tpLower;
3: Priority:=tpNormal;
4: Priority:=tpHigher;
5: Priority:=tpHighest;
end; {case}
Thread.Priority:=Priority;
end;
end;
Приоритет потока определяется его свойством Priority, типа TThreadPriority. Для изменения относительного приоритета потока создайте обработчик события OnChange для TrackBar2:
procedure TForm1.TrackBar2Change(Sender: TObject);
begin
SetThreadPriority(Thread1,TrackBar2.Position);
end;
Создание потока должно происходить при нажатии пользователем кнопки “Создать поток” (рис. 5). Для этого в обработчике OnClick соответствующей кнопки следует выполнить следующие действия:
procedure TForm1.Button2Click(Sender: TObject);
begin
Thread1:=TMyThread.Create(CheckBox1.Checked);
SetThreadPriority(Thread1,TrackBar2.Position);
if CheckBox1.Checked then begin
Button3.Enabled:=True;
Button4.Enabled:=False;
Button5.Enabled:=True;
end else begin
Button3.Enabled:=False;
Button4.Enabled:=True;
Button5.Enabled:=True;
end;
Button2.Enabled:=False;
end;
Для создания потока вызывается конструктор потока Create, определяя при этом режим создания потока (задержанный или нет). Затем задается начальный приоритет выполнения потока и обеспечивается согласованная работа кнопок при управлении потоком.
Конструктор потока Create имеет единственный параметр CreateSuspended, который определяет, как создается поток. Если параметр CreateSuspended установлен в True, то поток создается задержанным, т.е. код, расположенный в методе Execute не выполняется до тех пор, пока поток не будет перезапущен вызовом метода Resume. В противном случае поток сразу начинает работу. Режим создания потока будет определяться пользователем при помощи соответствующего компонента CheckBox, состояние которого (отмечено или нет) в свою очередь определяется свойством Сhecked, которое имеет значение логического типа.
Если поток создается задержанным, то его следует перезапустить, вызвав в обработчике OnСlick кнопки “Запустить поток” метод Resume:
procedure TForm1.Button3Click(Sender: TObject);
begin
SetThreadPriority(Thread1,TrackBar2.Position);
Thread1.Resume;
Button3.Enabled:=False;
Button4.Enabled:=True;
end;
Приостановить (временно) выполнение потока можно вызвав метод Suspend по нажатию кнопки “Остановить поток”. Остановка потока происходит не мгновенно, а спустя некоторое время, которое позволяет завершить код, который в данный момент времени может выполняться в методе Execute потока:
procedure TForm1.Button4Click(Sender: TObject);
begin
Thread1.Suspend;
Button3.Enabled:=True;
Button4.Enabled:=False;
end;
Уничтожение потока происходит при вызове метода Terminate(для наглядности после уничтожения потока правая колонка таблицы StringGrid2 заполняется пробелами):
procedure TForm1.Button5Click(Sender: TObject);
var i: integer;
begin
Thread1.Terminate;
Button2.Enabled:=True;
Button3.Enabled:=False;
Button4.Enabled:=False;
Button5.Enabled:=False;
for i:=0 to StringGrid2.RowCount-1 do
StringGrid2.Cells[1,i]:='';
end;
При этом, для того чтобы поток снова начал работать, необходимо его заново создать.
Для мониторинга текущего состояния потока необходимо добавить в ранее созданный обработчик события OnTimer следующий код:
procedure TForm1.Timer1Timer(Sender: TObject);
var STime: TSystemTime;
CrTime,ExTime,KrTime,UsTime: TFileTime;
begin
……………
if Thread1 <> nil then
if not Thread1.Terminated then begin
StringGrid2.Cells[1,0]:=IntToStr(Thread1.ThreadID);
StringGrid2.Cells[1,3]:=IntToStr(Thread1.Counter);
StringGrid2.Cells[1,4]:=IntToStr(GetThreadPriority(Thread1.Handle));
if GetThreadTimes(Thread1.Handle,CrTime,ExTime,KrTime,UsTime)
then begin
if FileTimeToSystemTime(CrTime,STime) then
StringGrid2.Cells[1,1]:=DateTimeToStr(SystemTimeToDateTime(STime));
if FileTimeToSystemTime(UsTime,STime) then
StringGrid2.Cells[1,2]:=TimeToStr(SystemTimeToDateTime(STime));
end;
Thread1.Counter:=0;
end;
……………
end;
Информация о номере потока (идентификаторе), который присвоен ему системой, хранится в свойстве ThreadID потока. Для получения приоритета потока используется функция Win32 GetThreadPriority. Для получения информации о времени создания, завершения, времени работы в режимах ядра и пользователя используется функция Win32 GetThreadTimes, которая возвращает перечисленные значения в формате времени TFileTime. Поэтому дальнейшие преобразования выполняются по аналогии с п.2 для процесса.
К п.4. Повторите действия, проделанные в п.3, для обеспечения возможности работы со вторым потоком в приложении. Для этого выделите мышкой компонент Panel2, на котором расположены все компоненты управления первым потоком, щелкните правой кнопкой мышки для вызова локального меню, при появлении которого выберите пункт Edit|Copy. Теперь щелкните правой кнопкой мышки на свободном пространстве формы и в появившемся меню выберите пункт Edit|Paste. После этого необходимо присвоить свойству Align только что размещенной панели Panel3 значение alRight. Кроме этого желательно, чтобы обе панели – Panel2 и Panel3 – имели одинаковую ширину.
Для обеспечения нормального управления вторым потоком в приложении, необходимо создать обработчики сообщений (по аналогии с п.3 для первого потока) для следующих компонентов:
- Form1 (событие OnCreate);
- Timer1 (OnTimer);
- Button6÷Button9 (OnClick);
- TrackBar3 (OnChange).
Не забудьте согласовать текст перечисленных обработчиков в соответствии с их назначением – управление работой второго потока.
К п.5. Для вызова Диспетчера задач Windows нажмите клавиши Ctrl+Alt+Delete и в появившемся диалоговом окне нажмите кнопку «Диспетчер задач».
К п.6. Создайте новое приложение, разместите на форме две кнопки “Закрыть” и “Запустить процесс”. Также на форму необходимо поместить компонент OpenDilog со страницы Dilogs, который будет использоваться для определения имени файла, который следует запустить. Сохранить приложение в ту же папку, что и предыдущее.
Процесс представляет собой приложение, которое содержит по крайней мере один поток.
Запустить процесс можно при помощи функции WinExec Windows API. Функция WinExec описана следующим образом:
UINT WinExec(
LPCSTR lpCmdLine, // путь к приложению
UINT uCmdShow // оконный стиль приложения
);
Таким образом, обработчик нажатия кнопки “Запустить процесс” будет иметь следующий вид:
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
WinExec(PChar(OpenDialog1.FileName),SW_SHOW);
end;
Запустить приложение, выбрать в диалоговом окне ранее созданное многопоточное приложение и запустить его.
Такого же результата можно добиться использованием функции Windows API CreateProcess, которая имеет более сложный список параметров.
Контрольные вопросы
1. Какой алгоритм планирования потоков используется в ОС Windows 2000 ?
2. Какие объекты ядра используются в ОС ?
3. Какие вы знаете классы приоритетов процесса ?
4. Какие вы знаете относительные приоритеты потоков ?
5. Как зависит приоритет потока от класса приоритета процесса и относительного приоритета потока ?
6. Средства Win32 API для управления процессами и потоками.
7. Средства Delphi для управления процессами и потоками.
8. Как осуществить хронометраж потоков ?
9. Чем в сущности является процесс в ОС Windows 2000 ?
10. Какая структура создается ОС Windows 2000 при создании процесса?
11. Чем владеет процесс в ОС Windows 2000 ?
12. Что является единицей работы в ОС Windows 2000 процесс или поток ?
13. Что такое контекст потока ?
14. Какая информация хранится в контексте потока ?
Лабораторная работа № 4