Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторный практикум «Основы разработки приложений Windows» книга 2.DOC
Скачиваний:
91
Добавлен:
10.05.2014
Размер:
827.9 Кб
Скачать

7. Процессы и потоки

Процессом (process) называют экземпляр выполняемой программы – не ход выполнения программы, а именно саму программу вместе с ее ресурсами – выделенным ей адресным пространством, динамически подключаемыми библиотеками и др. Таким образом, процесс вWin32, в противоречии со смыслом этого слова, не является динамическим объектом – это скорее оболочка, набор ресурсов, которые нужны для выполнения программы.

Сам же ход выполнения программы называют потоком (thread). Поток выполняется в рамках владеющего им процесса или, как говорят, в контексте процесса. Именно потокам операционная система выделяет кванты процессорного времени. Когда операционная система запускает приложение, она создает первичный поток процесса, составляющего это приложение. Таким образом, любой процесс имеет по крайней мере один поток выполнения. Первичный поток может запустить дополнительные потоки, которые в программе оформляются как более или менее самостоятельные функции. Эти потоки будут выполняться параллельно, конкурируя за процессорное время. Кроме того, первичный поток может запустить один или несколько процессов (программ); эти программы, точнее их потоки, также будут выполняться параллельно с породившим их родительским потоком.

Создание дочернего процесса

В качестве порожденного, или дочернего процесса может быть запущено любое приложение Windows, например, Калькулятор или Блокнот. Разумеется, для прикладных программных комплексов более полезным является запуск взаимосвязанных прикладныхпроцессов, между которыми разумным образом разделены функции всего программного комплекса.

Дочерний процесс создается вызовом функции CreateProcess(). Эта функция требует указания 10 параметров, большинством из которых в простых программах можно пренебречь. Однако для запуска дочернего процесса в программе придется создать и настроить должным образом некоторые служебные поля данных. Рассмотрим простейшую форму запуска дочернего процесса, в качестве которого мы используем, например, КалькуляторCALC.EXE(это не очень интересно по существу, но зато весьма наглядно).

STARTUPINFO si;//Служебная структурная переменная

PROCESS_INFORMATION pi;//Служебная структурная переменная

ZeroMemory(&si,sizeof(si));//Очистим переменную si

si.cb=sizeof(si);//Заносим в член si.cb размер переменной si

CreateProcess("e:\\win98\\calc.exe",NULL,NULL,NULL,

FALSE,0,NULL,NULL,&si,&pi);

Создание дочернего потока

Как уже отмечалось, система, запуская процесс (приложение), организует в нем первичный поток выполнения. Этот поток может с помощью функции CreateThread()создать еще один или несколько потоков, которые будут выполняться параллельно в контексте владеющего ими процесса (запущенной программы). С формальной точки зрения каждый поток представляет собой одну из функций, входящих в состав всего процесса; в отличие от остальных эту функцию можно назвать функцией потока или рабочей функцией. Рабочая функция может иметь любое содержимое, однако ее прототип жестко определен:

DWORD WINAPI Thread(LPVOID);

Как видно из прототипа, функция потока может принять в качестве параметра из вызывающего потока данное любого типа (число, адрес массива или структуры и др.) и использовать его далее по своему усмотрению.

Функция создания (запуска) потока CreateThread()требует указания шести параметров:

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,

DWORD dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags,

LPDWORD lpThreadId

);

Параметр lpThreadAttributesзадает адрес структуры с атрибутами защиты потока. В простой прикладной программе эта структура не нужна, и на месте этого атрибута указанNULL.

Параметр dwStackSizeпозволяет указать размер стека для данного потока; если этот параметр равен нулю, размер стека определяется по умолчанию.

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

Четвертый параметр служит для передачи в рабочую функцию произвольных данных из порождающей поток программы. Как уже отмечалось выше, его тип – LPVOID– позволяет передавать любой объем данных любого типа.

Параметр dwCreationFlagsопределяет режим создания потока. Если для него указать значениеCREATE_SUSPENDED, поток будет создан в спящем состоянии и фактически начнет выполняться лишь после вызова функцииResumeThread(). При нулевом значении этого параметра создаваемый поток начнет выполняться сразу после создания.

Наконец, в качестве последнего параметра указывается адрес переменной типа DWORD, в которую функция создания потока вернет его идентификатор.

Создав поток, функция CreateThread()возвращает его дескриптор типаHANDLE, который затем можно использовать при выполнении различных операций над потоком, например, приостановке выполнения потока с помощью функцииSuspend­Thread().

Скелетная схема программы, в которой запускается вторичный поток, выглядит следующим образом:

DWORDWINAPIMyThread(LPVOID);//Прототип рабочей функции

DWORDdwId;//Переменная для идентификатора потока

HANDLEhThread;//Переменная для дескриптора потока

hThread=CreateThread(NULL,0,MyThread,NULL,0,&dwId);

...//Продолжение программы порождающего потока

/*Рабочая функция потока*/

DWORDWINAPIMyThread(LPVOID){

//Любые действия, предназначенные для выполнения

//параллельно с порождающим потоком

return0;

}

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

Рассмотрим приложение, в которой выбором одного пункта меню дочерний поток создается, а выбором другого – уничтожается. Будучи запущен, вторичный поток начинает периодически инициировать вывод в главное окно приложения текущего времени (минуты и секунды). Приводимый пример позволит нам проиллюстрировать программное решение некоторых смежных вопросов: запуск и уничтожение потока по командам меню, циклическое безостановочное выполнение рабочей функции потока, взаимодействие функции потока с главным окном приложения и др. На рис. 7.1 приведен вид главного окна приложения с открытым меню и выведенным текущим временем.

Рис. 7.1. Окно приложения с порожденным потоком

/*Заголовочный файл Thread.h */

#define ID_1 101

#define ID_2 102

/*Файл ресурсов*/

#include "Thread.h"

Main MENU{

POPUP "Потоки"{

MENUITEM "ON", ID_1

MENUITEM "OFF", ID_2

}

}

/*Исходный текст программы (выборочно)*/

#include <windows.h>

#include <windowsx.h>

#include "Thread.h"

HWNDhWnd;//Дескриптор главного окна

HANDLEhThread1;//Дескриптор создаваемого потока

BOOLbFin=true;//Булева переменная для закрытия потока

/*Главная функция приложения*/

int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,int){

... //Регистрация класса окна

hWnd=CreateWindow(szClassName, //Создадим главное окно

"Threads",WS_OVERLAPPEDWINDOW,10,10,120,80,

HWND_DESKTOP,NULL,hInst,NULL);

ShowWindow(hWnd,SW_SHOWNORMAL);//Покажем главное окно

while(GetMessage(&msg,NULL,0,0))//Цикл обработки

DispatchMessage(&msg); //сообщений

return 0;

}

/*Оконная функция*/

LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,

WPARAM wParam,LPARAM lParam){

switch(msg){

HANDLE_MSG(hwnd,WM_COMMAND,OnCommand);

HANDLE_MSG(hwnd,WM_PAINT,OnPaint);

HANDLE_MSG(hwnd,WM_DESTROY,OnDestroy);

default:

return(DefWindowProc(hwnd,msg,wParam,lParam));

}

}

/*Оконная функция*/

void OnCommand(HWND,int id,HWND,UINT){

DWORD dwIDThread;//Идентификатор потока

switch(id){

case ID_1://Выбрали ON

bFin=true;//Разрешим потоку выполняться

hThread1=CreateThread(NULL,0,Thread1,//Создание потока

NULL,0,&dwIDThread);

break;

case ID_2://Выбрали OFF

bFin=false;//Завершим поток

CloseHandle(hThread1);//Освободим дескриптор потока

RECTrect;//Для координат главного окна

HDChdc=GetDC(hWnd);//Получим контекст устройства

GetClientRect(hWnd,&rect);//Получим координаты окна

FillRect(hdc,&rect,GetStockBrush//Закрасим окно белым

(WHITE_BRUSH));

ReleaseDC(hWnd,hdc);//Освободим контекст устройства

break;

}

}

/*Функция обработки сообщения WM_PAINT*/

void OnPaint(HWND hwnd){

char szT[10];

PAINTSTRUCT ps;

SYSTEMTIME st;

HDC hdc=BeginPaint(hwnd,&ps);

if(hThread1){//Только если есть поток

GetLocalTime(&st);//Получим текущее время

wsprintf(szT,"%d:%d",st.wMinute,st.wSecond);

TextOut(hdc,80,5,szT,strlen(szT));//Выведем в окно

}

EndPaint(hwnd,&ps);

}

/*Функция обработки сообщения WM_DESTROY*/

void OnDestroy(HWND){

bFin=FALSE;//Завершим поток при завершении приложения

PostQuitMessage(0);

}

/*Рабочая функция потока */

DWORD WINAPI Thread1(LPVOID){

while(bFin){//Пока выполнение потока разрешено

InvalidateRect(hWnd,NULL,TRUE);//Инициируем WM_PAINT

Sleep(1000);//Усыпим поток на 1 с

}

return 0;Завершим функцию потока (поток уничтожается)

}

Программа построена обычным образом и состоит из главной функции WinMain()и оконной функции, в которой обрабатыва­ются сообщенияWM_COMMAND,WM_PAINTиWM_DESTROY. При выборе пункта меню"ON"выполняются два действия: устанавливается в состояниеtrueвспомогательная булева переменнаяbFin, которая управляет циклическим выполнением рабочей функции потока, и вызовом функцииCreateThread()создается вторичный поток. Дескриптор потока сохраняется в переменнойhThread1. Начиная с этого момента в контексте приложения параллельно выполняются два потока: первичный, который “крутится” в цикле обработки сообщений, и вторичный, который инициирует периодический вывод в окно текущего времени.

Выбор пункта меню “OFF” приводит к выполнению более сложного алгоритма. ПеременнаяbFin сбрасывается в состояниеfalse, что приводит к разрыву цикла выполнения рабочей функции потока и к ее завершению. Далее вызовом функцииCloseHandle()освобождается ненужный более дескриптор потока. Если этого не сделать, то при повторных запусках потока выбором пункта меню “ON”, будут создаваться все новые и новые дескрипторы, что вряд ли разумно.

Последнее действие, выполняемое на этом участке программы, состоит в очистке главного окна приложения. Если окно не очистить, то последнее выведенное значение времени так и останется в окне, путая пользователя. Окно очищается вызовом функции Fillrect(), которая требует в качестве параметров дескриптор контекста устройства, адрес структуры типаRECTс координатами очищаемой области и дескриптор кисти, которой закрашивается окно. Для получения контекста устройства используется функцияGetDC(), координаты рабочей области окна определяются с помощью функцииGetClientRect(), а белая кисть для закрашивания берется со “склада” Windows.

Рабочая функция потока очень проста. В ней организован цикл whileдо сброса переменнойbFin. В цикл включена функцияSleep(), которая усыпляет поток на указанный интервал времени (1000 мс). При переводе переменнойbFinв состояниеfalseциклwhileразрывается и выполнением предложенияreturnрабочая функция потока завершает свою работу. Завершение рабочей функции автоматически приводит к уничтожению самого потока и освобождению всех занимаемых им ресурсов.

В цикл whileвключено единственное содержательное предложение – вызов функцииInvalidateRect(), которая инициирует посылку в главное окно приложения сообщенияWM_PAINTи, соответственно, перерисовку окна.

В функции OnPaint()обработки сообщенияWM_PAINTопределяется текущее время, значения минут и секунд преобразуются в символьную форму, и сформированная строка текста выводится в окно. Этот алгоритм выполняется только при ненулевом значении дескриптора потокаhThread1, что предохраняет от вывода в окно приложения текущего времени при запуске программы, когда в окно посылается первое сообщениеWM_PAINT.