Синхронизация процессов и потоков: семафоры




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

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

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

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

Чтобы предотвратить доступ программы к ресурсу R в то время, когда он исполь­зуется другой программой, предлагается следующее решение. Прежде всего, введите переменную flag, которая будет доступна из обеих программ. Начальное значение этой переменной равно 0. Прежде чем использовать ресурс R, любая из программ должна будет ожидать очистки флага (когда его значение снова будет равным 0), затем устанав­ливать значение flag в 1, потом работать с ресурсом, а после его освобождения снова сбрасывать значение флага в 0. То есть прежде чем начать работать с ресурсом R, каждая из программ должна выполнить следующий фрагмент кода:

 

while(flag)

; // Ожидание сброса флага

flag = 1;

//

//... Работа с ресурсом R...

//

flag= 0; // Сброс флага

 

Идея состоит в том, чтобы при ненулевом значении флага flagпроцесс не мог получить доступ к ресурсу. Теоретически это правильный подход, однако, в действитель­ности оказывается, что он не работает. Давайте разберемся, почему это происходит.

При использовании приведенного выше кода оказывается возможным одновре­менный доступ процессов к ресурсу R. Цикл whileявляется (на низком уровне) повторяющимся выполнением процессорных команд загрузки переменной flagи сравнения ее значения с 0. Когда значение flagоказывается равным 0, программа переходит к выполнению следующей инструкции, устанавливающей значение flagравным 1. Проблема заключается в том, что эти две инструкции могут выполняться в различных временных интервалах, между которыми доступ к переменной flagможет получить другой процесс, и, таким образом, оба процесса смогут одновременно получить доступ к ресурсу R. К примеру, представьте себе, что процесс А входит в цикл whileи обнаруживает, что значение flagравно 0. Однако, прежде чем этот процесс смог установить значение flagв 1, его временной интервал закончился, и система передала управление процессу В, который также выполнил цикл whileи тоже обнаружил, что значение flagравно 0. Таким образом, оба процесса определили, что они могут получить доступ к ресурсу R. Все дело в том, что процедура проверки и установки значения переменной flagне рассматривается в многозадачной операцион­ной системе как непрерывная операция. И поэтому не имеет значения, какие средства Вы будете использовать: так или иначе на уровне приложения не существует способа блокировки ресурса, который бы давал полную гарантию того, что этот ресурс в каждый момент времени будет доступен только одному процессу.

Решение проблемы сериализации на системном уровне элегантно и просто. Операционная система (в нашем случае Windows) содержит подпрограмму, кото­рая обеспечивает процесс проверки и, по возможности, установки значения флага как непрерываемую операцию. На языке разработчиков операционных систем это называется операцией проверки и установки значения (test and set operation). Флаги, используемые для управления сериализацией и обеспечения синхронизации процес­сов и потоков, получили название семафоры (semaphores).В следующих разделах рассматриваются функции Windows, осуществляющие поддержку семафоров.

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

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

Третий тип объектов синхронизации – это событие (event object).События могут служить для блокировки доступа к ресурсу до тех пор, пока другой процесс или поток не сигнализирует о его освобождении (то есть могут быть определенные типы событий – дополнительная информация, поступающая вместе с событием, а само событие сигнализирует о наступлении определенного типа события).

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

За исключением критических секций все объекты синхронизации могут исполь­зоваться для сериализации как потоков внутри одного процесса, так и собственно процессов. Фактически семафоры являются одним из средств организации взаимо­действия процессов в Windows.

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

Использование семафора для синхронизации потоков. Чтобы использовать семафор, необходимо сперва создать его при помощи функции CreateSemaphore():

 

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpAttr,

LONG InitialCount,

LONG MaxCount,

LPSTR lpszName);

 

Здесь параметр lpAttrпредставляет собой указатель на структуру, определяющую атрибуты доступа, либо может быть равен NULL, если атрибуты доступа не исполь­зуются (как, например, в случае Windows).

Семафор управляет доступом к определенному объекту для одной или более задач (процессов, потоков). Количество задач, которые могут иметь одновременный доступ к объекту, задается значением параметра MaxCount.Если значение этого параметра равно 1, то семафор работает как исключающий (mutex), разрешая доступ к объекту не более чем одной задаче в каждый момент времени.

Чтобы определить, сколько задач в настоящий момент имеет доступ к объекту, семафор использует счетчик. Если счетчик равен 0, доступ к объекту не будет разрешен до тех пор, пока какая-либо из задач не освободит семафор. Начальное значение счетчика семафора задается параметром InitialCount.Если значение этого параметра равно 0, то с самого начала все объекты, ожидающие семафор, будут блокированы до тех пор, пока семафор не будет освобожден. Обычно начальное значение счетчика семафора устанавливается равным единице или большему значе­нию, а это означает, что использование семафора разрешено по крайней мере одной задаче. В любом случае значение InitialCountдолжно быть неотрицательным и не превышать значение MaxCount.

Параметр lpszName должен быть указателем на строку, задающую имя семафора. Семафоры – это глобальные системные объекты, которые могут использоваться различными процессами. Поэтому когда два различных процесса открывают сема­фор, задавая одно и то же имя, используется один и тот же семафор. Таким образом, это является способом синхронизации процессов. Параметр может быть также равным NULL, и в этом случае семафор создается как локальный для данного процесса.

Функция CreateSeniaphore() при успешном завершении возвращает дескриптор созданного семафора, а при возникновении ошибки – NULL.

Вновь созданный семафор можно использовать при помощи двух функций WaitForSingleObject() и ReleaseSemaphore(), имеющих следующие прототипы:

 

DWORD WaitForSingleObject(HANDLE hObject,

DWORD dwHowLong);

 

BOOL ReleaseSemaphore (HANDLE hSema,

LONG Count,

LPLONG lpPrevCount);


Функция WaitForSingleObject() обеспечивает режим ожидания семафора (или другого типа объекта). Параметр hObject должен быть дескриптором объекта (в нашем случае – ранее созданного семафора). Параметр dwHowLong определяет время ожидания для программы в миллисекундах. Если в заданный период времени задача не получает доступа к объекту, функция возвращает значение ошибки таймаута. Если задаче предстоит ждать неопределенное время, значение этого параметра задается как INFINITE. При успешном завершении (т.е. если доступ к объекту получен до истечения заданного временного интервала) функция возвращает значение WAIT_OBJECT_0. Если же в течение заданного времени задача не получает доступа к объекту, функция возвращает значение WAIT_TIMEOUT. При успешном заверше­нии функции WaitForSingleObject() значение счетчика, связанного с семафором, уменьшается.

Функция ReleaseSemaphore() освобождает семафор, позволяя использовать его другому процессу или потоку. Параметр hSema является дескриптором семафора. Параметр Count определяет, какое значение должно быть добавлено к значению счетчика семафора. Обычно это значение равно 1. Параметр lpPrevCount является указателем на переменную типа LONG, в которую будет записано предыдущее значение счетчика семафора. Если это значение не требуется, значение параметра можно задать как NULL. При успешном завершении функция возвращает ненулевое значение, а при возникновении ошибки – нуль.

 

Пример 12-4. Использование семафора потоками

 

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

 

// Многопотоковая программа, иллюстрирующая

// синхронизацию при помощи стандартного семафора

// с использованием виртуального окна

 

#include <Windows.h>

#include <String.h>

#include <Stdio.h>

#include "Proc.h"

#define Procmax 5

 

LRESULT CALLBACK WindowFunc(HWND,UINT,WPARAM,LPARAM);

DWORD MyThread1(LPVOID); // Функция 1-го потока

DWORD MyThread2(LPVOID); // Функция 2-го потока

char szWinName[] = "МоеОкно"; // Имя класса окна

char str[255]; // Буфер строки вывода

int X=0, Y=0; // Текущие координаты строки

int procnum=0; // Количество активных процессов

DWORD Tid1; // Идентификатор 1-го потока

DWORD Tid2; // Идентификатор 3-го потока

int maxX, maxY; // Размеры экрана

HDC memdc; // DC виртуального окна

HBITMAP hbit; // Растр - это виртуальное окно

HBRUSH hbrush; // Дескриптор кисти

 

PROCESS_INFORMATION pinfo[Procmax];

HANDLE hSema; // Дескриптор семфора

TEXTMETRIC tm;

 

int WINAPI WinMain (HINSTANCE hThisInst,

HINSTANCE hPrevInst,

LPSTR lpszArgs,

int nWinMode)

{

HWND hwnd;

MSG msg;

WNDCLASS wcl;

HACCEL hAccel;

// Определить класс окна

wcl.hInstance=hThisInst; // Дескриптор приложения

wcl.lpszClassName=szWinName; // Имя класса окна

wcl.lpfnWndProc=WindowFunc; // Функция окна

wcl.style=0; // Стиль по умолчанию

wcl.hIcon=LoadIcon(NULL,IDI_APPLICATION); // Иконка

wcl.hCursor=LoadCursor(NULL,IDC_ARROW); // Курсор

wcl.lpszMenuName="MYMENU"; // Меню

wcl.cbClsExtra=0; // Без дополнительной

wcl.cbWndExtra=0; // информации

// Определить заполнение окна белым цветом

wcl.hbrBackground=

(HBRUSH)GetStockObject(WHITE_BRUSH);

if(!RegisterClass(&wcl)) // Зарегистр. класс окна

return 0;

// Создать окно

hwnd=CreateWindow(szWinName, // Имя класса

"Порождение процессов и потоков",

WS_OVERLAPPEDWINDOW,// Стиль окна

CW_USEDEFAULT, // Х-координата

CW_USEDEFAULT, // Y-координата

CW_USEDEFAULT, // Ширина окна

CW_USEDEFAULT, // Высота окна

HWND_DESKTOP, // Нет родит. окна

NULL, // Нет меню

hThisInst, // Дескрип. приложения

NULL); // Без дополит. аргументов

// Загрузить акселераторы

hAccel=LoadAccelerators(hThisInst,"MYMENU");

ShowWindow(hwnd,nWinMode); // Показать окно и

UpdateWindow(hwnd); // перерисовать содержимое

// Запустить цикл обработки сообщений

while(GetMessage (&msg,NULL,0,0))

if(!TranslateAccelerator(hwnd,hAccel,&msg))

{

TranslateMessage(&msg); // Использ.Клавиатуры

DispatchMessage (&msg); // Возврат к Windows

}

return msg. wParam;

}

 

// Следующая функция вызывается операционной системой

// Windows и получает в качестве параметров сообщения

// из очереди сообщений данного приложения

 

LRESULT CALLBACK WindowFunc(HWND hwnd,

UINT message,

WPARAM wParam,

LPARAM lParam)

{

HDC hdc;

PAINTSTRUCT paintstruct;

STARTUPINFO startin;

switch(message)

{

case WM_CREATE:

hSema = CreateSemaphore(NULL, 1, 1, "Mysem");

// Получаем размеры экрана

maxX=GetSystemMetrics(SM_CXSCREEN);

maxY=GetSystemMetrics(SM_CYSCREEN);

hdc=GetDC(hwnd); // Совмест. с окном растр

memdc=CreateCompatibleDC(hdc);

hbit=CreateCompatibleBitmap(hdc,maxX,maxY);

SelectObject(memdc,hbit);

hbrush=(HBRUSH)GetStockObject(WHITE_BRUSH);

SelectObject(memdc,hbrush);

PatBlt(memdc,0,0,maxX,maxY,PATCOPY);

ReleaseDC(hwnd,hdc);

break;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case ID_PROCESS:

if(procnum == Procmax)

{

MessageBox(hwnd,

"Нельзя создать больше",

"Ошибка", MB_OK);

break;

} // Не более чем Procmax

// Получить метрики текста */

GetTextMetrics(memdc, &tm);

sprintf(str, "Порождается процесс %d",

procnum);

TextOut(memdc, X, Y, str, strlen(str));

Y = Y+tm.tmHeight+tm.tmExternalLeading;

InvalidateRect(hwnd,NULL,1); //Сообщить

// Порождение нового процесса

startin.cb=sizeof(STARTUPINFO);

startin.lpReserved = NULL;

startin.lpDesktop = NULL;

startin.lpTitle = NULL;

startin.dwFlags =STARTF_USESHOWWINDOW;

startin.cbReserved2 = 0;

startin.lpReserved2 = NULL;

startin.wShowWindow = SW_SHOWMINIMIZED;

CreateProcess(NULL, "Test.exe", NULL,

NULL, FALSE, 0, NULL,

NULL, &startin,

&(pinfo[procnum]));

procnum++;

break;

case ID_KILLPROC:

if(procnum)

procnum--;

else

{

MessageBox(hwnd,

"Больше процессов нет",

"Ошибка", MB_OK);

break;

}

// Получить метрики текста

GetTextMetrics(memdc, &tm);

TerminateProcess

(pinfo[procnum].hProcess, 0);

sprintf(str, "Процесс %d завершен",

procnum);

TextOut(memdc, X, Y, str, strlen(str));

Y = Y+tm.tmHeight+tm.tmExternalLeading;

InvalidateRect(hwnd,NULL,1); //Сообщить

break;

case ID_THREAD:

CreateThread

(NULL, 0,

(LPTHREAD_START_ROUTINE)MyThread1,

(LPVOID)NULL, 0, &Tid1);

CreateThread

(NULL, 0,

(LPTHREAD_START_ROUTINE)MyThread2,

(LPVOID)NULL, 0, &Tid2);

InvalidateRect(hwnd, NULL, 1);

break;

case ID_HELP:

MessageBox(hwnd,

"F2: Новый процесс\n"

"F3: Завершить процесс\n"

"F4: Новый поток",

"Помощь:", MB_OK);

InvalidateRect(hwnd,NULL,1); //Сообщить

break;

}

break;

case WM_PAINT: // Перерисовка окна

hdc=BeginPaint(hwnd,&paintstruct); // Пол. DC

// Теперь копируем растр из памяти на экран

BitBlt(hdc,0,0,maxX,maxY,memdc,0,0,SRCCOPY);

EndPaint(hwnd,&paintstruct); // Освободить DC

break;

case WM_DESTROY: // Завершение программы

DeleteDC(memdc); // Удалить виртуальное окно

PostQuitMessage(0);

break;

default:

// Все сообщения, не обрабатываемые в данной

// функции, направляются на обработку по

// умолчанию

return DefWindowProc(hwnd,message,

wParam,lParam);

}

return 0;

}

 

//

// Начало выполнения 1-го потока

//

 

DWORD MyThread1(LPVOID param)

{

int i;

DWORD curTid = Tid1;

// Получить метрики текста

GetTextMetrics(memdc, &tm);

// Ждать разрешения доступа

if(WaitForSingleObject(hSema,10000)==WAIT_TIMEOUT)

{

MessageBox((HWND)param, "Поток 1 - таймаут",

"Ошибка семафора",

MB_OK);

return 0;

}

for(i=0; i<10; i++)

{

Sleep(500); // Задержка 0,5 сек

sprintf(str, "Поток %ld, сигнал %d", curTid, i);

TextOut(memdc, X, Y, str, strlen(str));

Y = Y + tm.tmHeight + tm.tmExternalLeading;

InvalidateRect((HWND)param, NULL, 1);

Beep(600, 250); // 600 Гц, 0,25 сек

}

ReleaseSemaphore(hSema, 1, NULL);

return 0;

}

 

//

// Начало выполнения 2-го потока

//

 

DWORD MyThread2(LPVOID param)

{

int i;

DWORD curTid = Tid2;

// Получить метрики текста

GetTextMetrics(memdc, &tm);

// Ждать разрешения доступа

if(WaitForSingleObject(hSema,10000)==WAIT_TIMEOUT)

{

MessageBox((HWND)param, "Поток 2 - таймаут",

"Ошибка семафора",

MB_OK);

return 0;

}

for(i=0; i<10; i++)

{

Sleep(200); // Задержка 0,2 сек

sprintf(str, "Поток %ld, сигнал %d", curTid, i);

TextOut(memdc, X, Y, str, strlen(str));

Y = Y + tm.tmHeight + tm.tmExternalLeading;

InvalidateRect((HWND)param, NULL, 1);

Beep(300, 250); // 300 Гц, 0,25 сек

}

ReleaseSemaphore(hSema, 1, NULL);

 

return 0;

}

Рис. 2.14. Иллюстрация координации потоков

 

На рис. 12.4 показано, что потоки выполняются строго последовательно, а не параллельно, как это было в предыдущем примере.

 

Обработка событий

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

 

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpAttr,

BOOL Manual, BOOL Initial,

LPSTR lpszName};

 

Здесь параметр lpAttr является указателем на структуру атрибутов доступа. Если такие атрибуты не используются, как в случае с Windows, значение этого параметра может быть NULL. Значение параметра Manual определяет, что должно происходить с объектом события после того, как событие произошло. Если это значение равно TRUE (не нуль), то событие может быть сброшено только при помощи вызова функции ResetEvent(). В противном случае событие сбрасывается автоматически после того, как к нему получает доступ какой-либо блокированный процесс. Значение Initial задает начальное состояние события. Если оно равно TRUE, событие сразу устанавливается (сразу же подается сигнал о наступлении соответствующей ситуации). Если же это значение равно FALSE, событие инициализируется как сброшенное.

Параметр lpszName является указателем на строку, содержащую имя события. События представляют собой глобальные системные объекты, доступные всем процессам в системе. Поэтому, если разные процессы открывают объект события, задавая одно и то же имя, используется один и тот же системный объект. Этот параметр может быть задан как NULL, и в этом случае объект события определяется как локальный для данного процесса.

Функция CreateEvent() возвращает дескриптор созданного объекта события либо NULL при возникновении ошибки.

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

Для того чтобы сигнализировать о наступлении события, используется функция SetEvent():

 

BOOL SetEvent(HANDLE hEventObject);

 

Здесь параметр hEventObject является дескриптором ранее созданного события. После вызова функции SetEvent() процесс или поток, ожидающий этого события и вызвавший для этого функцию WaitForSingleObject(), продолжит свое выполнение.

Чтобы понять, как нужно работать с объектами событий, модифицируйте преды­дущую программу следующим образом. Во-первых, определите глобальный дескрип­тор (HANDLE)hEvent. Затем при создании главного окна создайте событие, добавив в код обработки сообщения WM_CREATE строку

 

hEvent = CreateEvent(NULL, FALSE, FALSE,"Myevent");

 

И наконец, измените функции MyThread1() и MyThread2() следующим образом:

 

//

// Начало выполнения 1-го потока

//

 

DWORD MyThread1(LPVOID param)

{

int i;

DWORD curTid = Tid1;

// Получить метрики текста

GetTextMetrics(memdc, &tm);

// Ждать события

if(WaitForSingleObject(hEvent,10000)==WAIT_TIMEOUT)

{

MessageBox((HWND)param, "Поток 1 - таймаут",

"Ошибка семафора",

MB_OK);

return 0;

}

for(i=0; i<10; i++)

{

Sleep(500); // Задержка 0,5 сек

sprintf(str, "Поток %ld, сигнал %d", curTid, i);

TextOut(memdc, X, Y, str, strlen(str));

Y = Y + tm.tmHeight + tm.tmExternalLeading;

InvalidateRect((HWND)param, NULL, 1);

Beep(600, 250); // 600 Гц, 0,25 сек

}

return 0;

}

 

//

// Начало выполнения 2-го потока

//

 

DWORD MyThread2(LPVOID param)

{

int i;

DWORD curTid = Tid2;

// Получить метрики текста

GetTextMetrics(memdc, &tm);

for(i=0; i<10; i++)

{

Sleep(200); // Задержка 0,2 сек

sprintf(str, "Поток %ld, сигнал %d", curTid, i);

TextOut(memdc, X, Y, str, strlen(str));

Y = Y + tm.tmHeight + tm.tmExternalLeading;

InvalidateRect((HWND)param, NULL, 1);

Beep(300, 250); // 300 Гц, 0,25 сек

}

// Послать информацию о событии

SetEvent(hEvent);

return 0;

}

 

Теперь при выполнении программы поток MyThreadl() будет блокирован до окончания выполнения потока MyThread2(), который сигнализирует об этом.

 

Пример 12-5. Многопотоковая обработка событий

 

// Многопотоковая программа, иллюстрирующая

// обработку событий

// с использованием виртуального окна

 

#include <Windows.h>

#include <String.h>

#include <Stdio.h>

#include "Proc.h"

#define Procmax 5

 

LRESULT CALLBACK WindowFunc(HWND,UINT,WPARAM,LPARAM);

DWORD MyThread1(LPVOID); // Функция 1-го потока

DWORD MyThread2(LPVOID); // Функция 2-го потока

char szWinName[] = "МоеОкно"; // Имя класса окна

char str[255]; // Буфер строки вывода

int X=0, Y=0; // Текущие координаты строки

int procnum=0; // Количество активных процессов

DWORD Tid1; // Идентификатор 1-го потока

DWORD Tid2; // Идентификатор 3-го потока

int maxX, maxY; // Размеры экрана

HDC memdc; // DC виртуального окна

HBITMAP hbit; // Растр - это виртуальное окно

HBRUSH hbrush; // Дескриптор кисти

 

PROCESS_INFORMATION pinfo[Procmax];

HANDLE hEvent; // Дескриптор события

TEXTMETRIC tm;

 

int WINAPI WinMain (HINSTANCE hThisInst,

HINSTANCE hPrevInst,

LPSTR lpszArgs,

int nWinMode)

{

HWND hwnd;

MSG msg;

WNDCLASS wcl;

HACCEL hAccel;

// Определить класс окна

wcl.hInstance=hThisInst; // Дескриптор приложения

wcl.lpszClassName=szWinName; // Имя класса окна

wcl.lpfnWndProc=WindowFunc; // Функция окна

wcl.style=0; // Стиль по умолчанию

wcl.hIcon=LoadIcon(NULL,IDI_APPLICATION); // Иконка

wcl.hCursor=LoadCursor(NULL,IDC_ARROW); // Курсор

wcl.lpszMenuName="MYMENU"; // Меню

wcl.cbClsExtra=0; // Без дополнительной

wcl.cbWndExtra=0; // информации

// Определить заполнение окна белым цветом

wcl.hbrBackground=

(HBRUSH)GetStockObject(WHITE_BRUSH);

if(!RegisterClass(&wcl)) // Зарегистр. класс окна

return 0;

// Создать окно

hwnd=CreateWindow(szWinName, // Имя класса

"Порождение процессов и потоков",

WS_OVERLAPPEDWINDOW,// Стиль окна

CW_USEDEFAULT, // Х-координата

CW_USEDEFAULT, // Y-координата

CW_USEDEFAULT, // Ширина окна

CW_USEDEFAULT, // Высота окна

HWND_DESKTOP, // Нет родит. окна

NULL, // Нет меню

hThisInst, // Дескрип. приложения

NULL); // Без дополит. аргументов

// Загрузить акселераторы

hAccel=LoadAccelerators(hThisInst,"MYMENU");

ShowWindow(hwnd,nWinMode); // Показать окно и

UpdateWindow(hwnd); // перерисовать содержимое

// Запустить цикл обработки сообщений

while(GetMessage (&msg,NULL,0,0))

if(!TranslateAccelerator(hwnd,hAccel,&msg))

{

TranslateMessage(&msg); // Использ.Клавиатуры

DispatchMessage (&msg); // Возврат к Windows

}

return msg. wParam;

}

// Следующая функция вызывается операционной системой

// Windows и получает в качестве параметров сообщения

// из очереди сообщений данного приложения

LRESULT CALLBACK WindowFunc(HWND hwnd,

UINT message,

WPARAM wParam,

LPARAM lParam)

{

HDC hdc;

PAINTSTRUCT paintstruct;

STARTUPINFO startin;

switch(message)

{

case WM_CREATE:

hEvent = CreateEvent(NULL, FALSE, FALSE,

"Myevent");

// Получаем размеры экрана

maxX=GetSystemMetrics(SM_CXSCREEN);

maxY=GetSystemMetrics(SM_CYSCREEN);

hdc=GetDC(hwnd); // Совмест. с окном растр

memdc=CreateCompatibleDC(hdc);

hbit=CreateCompatibleBitmap(hdc,maxX,maxY);

SelectObject(memdc,hbit);

hbrush=(HBRUSH)GetStockObject(WHITE_BRUSH);

SelectObject(memdc,hbrush);

PatBlt(memdc,0,0,maxX,maxY,PATCOPY);

ReleaseDC(hwnd,hdc);

break;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case ID_PROCESS:

if(procnum == Procmax)

{

MessageBox(hwnd,

"Нельзя создать больше",

"Ошибка", MB_OK);

break;

} // Не более чем Procmax

// Получить метрики текста */

GetTextMetrics(memdc, &tm);

sprintf(str, "Порождается процесс %d",

procnum);

TextOut(memdc, X, Y, str, strlen(str));

Y = Y+tm.tmHeight+tm.tmExternalLeading;

InvalidateRect(hwnd,NULL,1); //Сообщить

// Порождение нового процесса

startin.cb=sizeof(STARTUPINFO);

startin.lpReserved = NULL;

startin.lpDesktop = NULL;

startin.lpTitle = NULL;

startin.dwFlags =STARTF_USESHOWWINDOW;

startin.cbReserved2 = 0;

startin.lpReserved2 = NULL;

startin.wShowWindow = SW_SHOWMINIMIZED;

CreateProcess(NULL, "Test.exe", NULL,

NULL, FALSE, 0, NULL,

NULL, &startin,

&(pinfo[procnum]));

procnum++;

break;

case ID_KILLPROC:

if(procnum)

procnum--;

else

{

MessageBox(hwnd,

"Больше процессов нет",

"Ошибка", MB_OK);

break;

}

// Получить метрики текста

GetTextMetrics(memdc, &tm);

TerminateProcess

(pinfo[procnum].hProcess, 0);

sprintf(str, "Процесс %d завершен",

procnum);

TextOut(memdc, X, Y, str, strlen(str));

Y = Y+tm.tmHeight+tm.tmExternalLeading;

InvalidateRect(hwnd,NULL,1); //Сообщить

break;

case ID_THREAD:

CreateThread

(NULL, 0,

(LPTHREAD_START_ROUTINE)MyThread1,

(LPVOID)NULL, 0, &Tid1);

CreateThread

(NULL, 0,

(LPTHREAD_START_ROUTINE)MyThread2,

(LPVOID)NULL, 0, &Tid2);

InvalidateRect(hwnd, NULL, 1);

break;

case ID_HELP:

MessageBox(hwnd,

"F2: Новый процесс\n"

"F3: Завершить процесс\n"

"F4: Новый поток",

"Помощь:", MB_OK);

InvalidateRect(hwnd,NULL,1); //Сообщить

break;

}

break;

case WM_PAINT: // Перерисовка окна

hdc=BeginPaint(hwnd,&paintstruct); // Пол. DC

// Теперь копируем растр из памяти на экран

BitBlt(hdc,0,0,maxX,maxY,memdc,0,0,SRCCOPY);

EndPaint(hwnd,&paintstruct); // Освободить DC

break;

case WM_DESTROY: // Завершение программы

DeleteDC(memdc); // Удалить виртуальное окно

PostQuitMessage(0);

break;

default:

// Все сообщения, не обрабатываемые в данной

// функции, направляются на обработку по

// умолчанию

return DefWindowProc(hwnd,message,

wParam,lParam);

}

return 0;

}

//

// Начало выполнения 1-го потока

//

DWORD MyThread1(LPVOID param)

{

int i;

DWORD curTid = Tid1;

// Получить метрики текста

GetTextMetrics(memdc, &tm);

// Ждать события

if(WaitForSingleObject(hEvent,10000)==WAIT_TIMEOUT)

{

MessageBox((HWND)param, "Поток 1 - таймаут",

"Ошибка семафора",

MB_OK);

return 0;

}

for(i=0; i<10; i++)

{

Sleep(500); // Задержка 0,5 сек

sprintf(str, "Поток %ld, сигнал %d", curTid, i);

TextOut(memdc, X, Y, str, strlen(str));

Y = Y + tm.tmHeight + tm.tmExternalLeading;

InvalidateRect((HWND)param, NULL, 1);

Beep(600, 250); // 600 Гц, 0,25 сек

}

return 0;

}

//

// Начало выполнения 2-го потока

//

DWORD MyThread2(LPVOID param)

{

int i;

DWORD curTid = Tid2;

// Получить метрики текста

GetTextMetrics(memdc, &tm);

for(i=0; i<10; i++)

{

Sleep(200); // Задержка 0,2 сек

sprintf(str, "Поток %ld, сигнал %d", curTid, i);

TextOut(memdc, X, Y, str, strlen(str));

Y = Y + tm.tmHeight + tm.tmExternalLeading;

InvalidateRect((HWND)param, NULL, 1);

Beep(300, 250); // 300 Гц, 0,25 сек

}

// Послать информацию о событии

SetEvent(hEvent);

return 0;

}

Выполнение многопотоковой программы иллюстрируется точно так, как показано на рис. 12.4: опять потоки выполняются не параллельно, а строго последовательно.


Глава 13. Приемы программного управления вычислительным
процессом



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2017-03-31 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: