- •Часть 2 (продолжение): Прикладное программирование в Windows
- •Глава 8. Виртуальное окно: работа с текстом
- •8.1. Оконные координаты, цвета, отображение строк
- •Кодировка rgb-значений
- •Размеры системных метрик
- •8.2. Виртуальное окно
- •Способы заполнения прямоугольной области
- •8.3. Изменение шрифтов
- •Макроимена распространенных шрифтов
- •8.4. Создание собственных шрифтов
- •Глава 9. Программирование графики
- •9.1. Логическая система координат
- •Рекомендуемый самый яркий состав цветов
- •9.2. Пиксели, линии, дуги, прямоугольники, эллипсы и секторы
- •9.3. Работа с перьями и кистями
- •Типы линий, создаваемых пером
- •9.4. Технология виртуального окна
- •9.5. Режимы отображения и области вывода
- •Текущий режим отображения (mappind mode)
- •Глава 10. Работа с панелями инструментов
- •10.1. Общие элементы управления
- •Общие элементы управления
- •10.2. Подключение и инициализация общих элементов управления
- •10.3. Работа с панелью инструментов
- •Начальные состояния кнопок панели инструментов
- •Стиль кнопки определяется (значения поля fsStyle)
- •Сообщения к панели инструментов
- •10.4. Создание растрового изображения для панели инструментов
- •10.5. Включение подсказок
- •Глава 11. Спины, ползунки и индикаторы процессов
- •11.1. Работа со спином
- •Стили «up-down control»
- •Сообщения, которые можно посылать спину
- •11.2. Создание спина с «приятельским» окном
- •11.3. Работа с ползунком
- •Значения стилей для ползунка
- •Перечень сообщений, которые можно посылать ползунку
- •Перечень нотификационных сообщения ползунка
- •11.4. Индикатор процесса
- •Управляющие сообщения индикатору
- •Глава 12. Многозадачность: процессы и потоки
- •12.1. Создание нового процесса (отдельной задачи)
- •Разрешенные значения поля dwFlags
- •12.2. Многопотоковые программы
- •12.3. Работа с несколькими потоками
- •12.4. Синхронизация процессов и потоков: семафоры
- •12.5. Обработка событий
- •Глава 13. Приемы программного управления вычислительным процессом
- •13.1. Использование функций Проводника Explorer для работы в файловой системе
- •Управляющая информация Проводника
- •Режимы обработки файлов
- •13.2. Создание собственных динамических библиотек (dll-файлов)
- •13.3. Работа с буфером обмена Clipboard
- •Функции подсистемы Clipboard
- •Свойства выделяемого блока памяти
- •Форматы и типы данных
- •Литература
12.4. Синхронизация процессов и потоков: семафоры
Синхронизация. При работе со многими процессами или потоками нередко требуется синхронизировать функционирование двух или более из них. Причиной этого чаще всего является необходимость обеспечить доступ нескольких потоков к одному и тому же ресурсу, который может использоваться одновременно только одним потоком. Например, если один поток записывает информацию в файл, все другие потоки не могут в данный момент времени этот файл использовать. Механизм предотвращения таких ситуаций называется сериализацией. Другой причиной может быть ожидание одним потоком некоего события, которое может наступить лишь при выполнении другого потока. Для таких случаев должны быть предусмотрены специальные средства, с помощью которых первый поток будет переведен в состояние ожидания до возникновения соответствующего события, а после этого продолжит выполнение.
Выполняющаяся задача может находиться в одном из двух основных состояний. Во-первых, она может выполняться (или быть готовой к выполнению при получении системного временного кванта). Во-вторых, задача может быть блокирована в ожидании освобождения некоторого ресурса или возникновения некоторого события, и в этом случае ее выполнение приостанавливается до освобождения ресурса или наступления события.
Тем, кто еще не знаком с проблемами синхронизации и сериализации, а также с их решением при помощи семафоров, будет полезен следующий раздел, в котором содержится соответствующая информация (если же Вам все это уже известно, можете этот раздел пропустить).
Проблема сериализации. 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 показано, что потоки выполняются строго последовательно, а не параллельно, как это было в предыдущем примере.
