Н. А. Литвиненко



Pdf просмотр
страница13/15
Дата28.11.2016
Размер8.29 Mb.
Просмотров3126
Скачиваний1
1   ...   7   8   9   10   11   12   13   14   15
П
Р И МЕ Ч А Н И Е

В файле включений winnt.h определено объединение typedef union _LARGE_INTEGER
{ struct {DWORD LowPart; LONG HighPart;}; struct {DWORD LowPart; LONG HighPart;} u;
LONGLONG QuadPart;
} LARGE_INTEGER;
Функция
GetThreadTimes() позволит получить три временные характеристики:

kernelTime = kernel.QuadPart
— время работы ядра протока;

userTime = user.QuadPart
— время выполнения собственного кода потока;

totalTime = Exit.QuadPart-Create.QuadPart
— полное время работы потока.

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

223
Результат получен в виде 64-разрядного числа типа
__int64
, для преобразования которого в текстовое представление проще всего использовать функцию
_stprintf()
, как мы это делали в листинге 1.13. Нужно только использовать фор- мат 64-разрядного целого числа —
%I64d
П
Р И МЕ Ч А Н И Е

Можно было воспользоваться 64
- разрядным аналогом функции itoa()
: char* _i64toa(__int64 Val, char *DstBuf, int Radix); или wchar_t* _i64tow(__int64 Val, wchar_t *DstBuf, int Radix);

Рис.
6.2.
Измерение времени работы потока
Высокоточное измерение времени

Однако функция
GetThreadTimes() не позволит измерить малый промежуток внутри одного кванта времени (около 20 миллисекунд, 1 мс = 10
–3
с). Для этих це- лей в Windows предусмотрены две функции.
Функция
QueryPerformanceFrequency() позволит измерить частоту счетчика вы- полнения с высоким разрешением:
BOOL WINAPI QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency); а функция
QueryPerformanceCounter() возвращает его текущий отсчет.
BOOL WINAPI QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);
В качестве параметра используется указатель на объединение
LARGE_INTEGER
, опи- санное в файле включений winnt.h
Обе функции возвращают
FALSE
, если не удалось найти счетчик выполнения.
В качестве примера (листинг 6.4) "измерим" время работы цикла, вид окна прило- жения приведен на рис. 6.3.
Листинг 6.4. Тест для высокоточного измерения временного интервала

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
PAINTSTRUCT ps;
HDC hdc;

Глава 6

224
TCHAR str[60], tmp[20]; int i, sum;
LARGE_INTEGER frequency, Start, End; static __int64 totalTime; switch (message)
{ case WM_COMMAND: switch (LOWORD(wParam))
{ case ID_CYCLE:

QueryPerformanceFrequency(&frequency);

Sleep(0);

QueryPerformanceCounter(&Start);

// Измеряемый код
for (i = sum = 0; i < 1000; i++) sum += i;

//////////////////////////////////////////

QueryPerformanceCounter(&End);
totalTime = (End.QuadPart - Start.QuadPart)*1000000/

frequency.QuadPart;

InvalidateRect(hWnd, NULL, TRUE);
break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam);
} break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps);
_tcscpy(str, _T("Время работы цикла в мкс: "));
_i64tot(totalTime, tmp, 10);
_tcscat(str, tmp);
TextOut(hdc, 0, 0, str, _tcslen(str));
EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam);
} return 0;
}
Для хранения временных характеристик опишем переменные:
LARGE_INTEGER frequency, Start, End; static __int64 totalTime;

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

225
Вычислительный алгоритм запустим на выполнение через меню главного окна, его идентификатор
ID_CYCLE
. Перед началом вычислений измерим частоту счетчика и снимем первый отсчет:
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&start);
П
Р И МЕ Ч А Н И Е

Мы воспользовались функцией Sleep(0)
перед первым отсчетом времени, чтобы вычисления гарантированно начались с началом выделенного потоку кванта времени.
Функция Sleep(msec) обеспечивает задержку выполнения потока на msec миллисе- кунд, удаляя поток из очереди ожидания процессорного времени; если же аргумент равен 0, поток отдает остаток кванта времени и ждет получения следующего кванта.
По завершению цикла снимаем второй отсчет:
QueryPerformanceCounter(&end); и вычисляем время между двумя отсчетами в микросекундах (1 мкс = 10
–6
с): totalTime = (end.QuadPart-Start.QuadPart)*1000000/frequency.QuadPart;
Здесь поле
QuadPart имеет тот же тип
__int64
, что и переменная totalTime
П
Р И МЕ Ч А Н И Е

Мы использовали масштабный множитель 1000000
, чтобы результат вывести в мик- росекундах. Больший множитель использовать нет смысла, поскольку накладные рас- ходы на вызов функции QueryPerformanceCounter()
составляют около 1
мкс.
Сформируем строку для вывода в сообщении
WM_PAINT
. Для преобразования в строку
64-битного значения используем функцию
_i64tot()

Рис.
6.3.
Высокоточное измерение времени
Приоритеты потоков

Распределением процессорного времени между потоками, выполняемыми в опера- ционной системе, занимается планировщик потоков, являющийся важнейшим ком- понентом Windows. Каждому потоку при запуске присваивается свой уровень
приоритета (от 0 — самый низкий приоритет, до 31 — самый высокий приоритет).
Причем 0 приоритет зарезервирован за специальным системным потоком "очистки свободных страниц памяти" и пользовательским потокам не присваивается.
Уровень приоритета потока складывается из двух компонентов: класса приорите- та процесса и относительного приоритета потока. Рассмотрим их подробно.

Глава 6

226
В табл. 6.1 приведены идентификаторы, которыми можно определить класс приоритета процесса при его создании в параметре dwCreationFlags функции
CreateProcess()
Этот класс будет применяться для всех потоков, созданных процессом.
Таблица 6.1.

Классы приоритета

процесса

Класс приоритета

Идентификатор

Значение

Real

time
(реального времени)
REALTIME_PRIORITY_CLASS
0x00000100
High
(высокий)
HIGH_PRIORITY_CLASS
0x00000080
Above normal
(выше обычного
)
ABOVE_NORMAL_PRIORITY_CLASS
0x00008000
Normal (
обычный
)
NORMAL_PRIORITY_CLASS
0x00000020
Below normal (
ниже обычного
)
BELOW_NORMAL_PRIORITY_CLASS
0x00004000
Idle (
простаивающий
)
IDLE_PRIORITY_CLASS
0x00000040

По умолчанию (параметр dwCreationFlags
=
0) задается класс приоритета
Normal и большинство процессов создается именно таким образом. Не следует задавать процессу слишком высокий класс — это может сказаться на производительности системы в целом. Необходимо помнить, что класс Real-time используется лишь в исключительных случаях.
Функция
GetPriorityClass()
:
DWORD WINAPI GetPriorityClass(HANDLE hProcess); возвращает информацию о классе приоритета процесса, а другая функция
SetPriorityClass()
:
BOOL WINAPI SetPriorityClass(HANDLE hProcess, DWORD dwPriorityClass); позволяет изменить класс приоритета работающего приложения, который будет действовать лишь на вновь созданные потоки.
Определить класс приоритета текущего процесса можно следующим образом:
DWORD Priority = GetPriorityClass(GetCurrentProcess()); где функция
GetCurrentProcess() вернет дескриптор текущего процесса.
Изменить класс приоритета процесса после его создания можно так:
SetPriorityClass(pInfo.hProcess, HIGH_PRIORITY_CLASS);
Однако класс приоритета процесса является лишь базовым уровнем, относительно которого определяется приоритет потока. При создании потока функцией
CreateThread()
он всегда создается с приоритетом Normal, который задает базо- вый уровень для текущего класса.
Для изменения приоритета созданного потока используется функция
SetThreadPriority()
:
BOOL WINAPI SetThreadPriority(HANDLE hThread, int nPriority); где nPriority
— относительный приоритет потока.
Приведем для справки в табл. 6.2 приоритеты потоков из книги Дж. Рихтера.

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

227
Таблица_6.2.___Приоритеты_потоков_Относительный__приоритет_потока__Класс_приоритета_потока__Idle'>Таблица 6.2.

Приоритеты потоков
Относительный

приоритет потока

Класс приоритета потока

Idle
Below
Normal
Normal
Above
Normal
High
Real-
time
Time-critical
(критичный по времени)
15 15 15 15 15 31
Highest
(высший)
6 8
10 12 15 26
Above normal
(выше обычного)
5 7
9 11 14 25
Normal
(обычный)
4 6
8 10 13 24
Below normal
(ниже обычного)
3 5
7 9
12 23
Lowest
(низший)
2 4
6 8
11 22
Idle
(простаивающий)
1 1
1 1
1 16
Можно задать 7 различных приоритетов потока, а абсолютное значение складыва- ется из базового приоритета класса и относительного приоритета потока. Из табли- цы видно, что с относительным приоритетом Time-critical для всех классов, кроме
Real-time, абсолютный приоритет — 15, а с приоритетом Idle — 1. Это сделано, чтобы ограничить область "динамического приоритета", в которой должны нахо- диться пользовательские потоки.
Идентификаторы, необходимые для установки относительного приоритета потока, определены в файле включений winbase.h
, их значения приведены в табл. 6.3.
Таблица
6.3.
Идентификаторы

относительных

приоритетов

потока

Относительный

приоритет потока

Идентификатор

Значение

Time-critical
THREAD_PRIORITY_TIME_CRITICAL
15
Highest
THREAD_PRIORITY_HIGHEST
2
Above normal
THREAD_PRIORITY_ABOVE_NORMAL
1
Normal
THREAD_PRIORITY_NORMAL
0
Below normal
THREAD_PRIORITY_BELOW_NORMAL
-1
Lowest
THREAD_PRIORITY_LOWEST
-2
Idle
THREAD_PRIORITY_IDLE
-15

Чтобы создать поток с относительным приоритетом, например, Highest, обычно создают "приостановленный поток", изменяют его приоритет и запускают на вы- полнение функцией
ResumeThread()
:
DWORD WINAPI ResumeThread(HANDLE hThread);

Глава 6

228
Например,
HANDLE hThread; hThread = CreateThread(NULL, 0, MyThread, NULL, CREATE_SUSPENDED, NULL);
SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);
ResumeThread(hThread);
Текущее значение относительного приоритета потока можно узнать из возвращае- мого значения функции
GetThreadPriority()
: int WINAPI GetThreadPriority(HANDLE hThread);
В нашем случае, если бы мы добавили в предыдущий пример строку: int priority = GetThreadPriority(hThread); то получили бы значение равное
2
. Однако истинное значение может быть больше, и это связано с тем, что планировщик потоков может самостоятельно повышать приоритет потоков, но только в области приоритетов 1—15, поэтому эту область и называют областью динамического приоритета. Единственное, в чем мы можем быть уверены, так это в том, что приоритет потока не ниже установленного.
П
Р И МЕ Ч А Н И Е

Можно изменить приоритет и у работающего потока.
Планировщик потоков просматривает все потоки, претендующие на процессорное время (потоки, находящиеся в режиме ожидания, исключаются из распределения).
Просматриваются потоки, начиная со старшего приоритета — 31, и, как только бу- дет найден поток, ожидающий очереди, ему будет отдан квант времени (около
20 мс). Очевидно, чем меньше приоритет потока, тем меньше у него шансов на по- лучение кванта времени, и "сборщик мусора" с приоритетом 1 получит управление только тогда, когда системе совсем уже нечем будет заняться. С другой стороны, если мы зададим слишком высокий приоритет, то можем заблокировать некоторые системные утилиты, что может привести к "тупиковой" ситуации.
Синхронизация потоков

в пользовательском режиме

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

Нередко возникает простая задача синхронизации, когда нескольким потокам тре- буется изменить значение одной глобальной переменной. В этом случае можно ре- шить задачу, используя Interlocked-функции. Эти функции называют атомарными, имея в виду неделимость операции: они выполняются очень быстро, отнимая при- мерно 50 тактов процессорного времени.

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

229
Представим ситуацию: long variable = 0;
DWORD WINAPI Thread1(PVOID param)
{ variable++; return 0;
}
DWORD WINAPI Thread2(PVOID param)
{ variable++; return 0;
}
Два потока пытаются увеличить значение переменной variable
. Допустим, первый поток считывает значение переменной variable
,
равное
0
, и в этот момент про- изойдет передача управления второму потоку, который также считывает значение переменной variable
,
равное
0
, увеличивает значение на
1
и записывает результат обратно в глобальную переменную. Теперь первый поток снова получает управле- ние, увеличивает значение на
1
и записывает результат обратно в переменную.
После завершения работы потоков переменная variable равна
1
, а не
2
, как нам бы хотелось.
Для того чтобы подобная операция выполнилась корректно, необходимо на время операции чтения/записи заблокировать обращение к переменной со стороны другого потока. Это достигается в Interlocked-функциях, которые устанавливают на время операции специальный флаг, указывающий, что данный адрес памяти заблокирован:

LONG InterlockedExchangeAdd(PLONG plAddend, LONG lIncrement); функция позволяет увеличить значение переменной
*plAddend на величину lIncrement
(или уменьшить, если параметр имеет отрицательное значение);

LONG InterlockedExchange(PLONG plTarget, LONG lVal); функция заменяет значение переменной
*plTarget на lVal
;

PVOID InterlockedExchangePointer(PVOID* ppvTarget, PVOID pvVal); функция заменяет значение указателя
*ppvTarget на pvVal
Возвращаемым значением функций является новое значение переменной, на кото- рую указывает первый параметр.
П
Р И МЕ Ч А Н И Е

Имеется дополнительный набор атомарных функций, реализованных в операционной системе
Vista и
Windows
7, в том числе их 64
- битные аналоги. Подробнее в
MSDN.
Если вернуться к нашему примеру, то нужно было бы сделать так:
DWORD WINAPI Thread1(PVOID param)
{
InterlockedExchangeAdd(&variable, 1); return 0;

Глава 6

230
}
DWORD WINAPI Thread2(PVOID param)
{
InterlockedExchangeAdd(&variable, 1); return 0;
}
Для двух других функций постройте пример самостоятельно.
Критические секции (
critical section)
Критические секции реализованы по принципу "эстафетной палочки", которой об- мениваются потоки, проверяя доступность ресурса, и являются наиболее эконо- мичным способом синхронизации потоков.
Вначале необходимо объявить переменную типа структуры
CRITICAL_SECTION
на глобальном уровне:
CRITICAL_SECTION CriticalSection;
Перед использованием переменная должна быть инициализирована функцией
InitializeCriticalSection()
:
VOID WINAPI InitializeCriticalSection(LPCRITICAL_SECTION lpCrSection);
Для работы с критическими секциями используются две функции, которые прини- мают в качестве параметра указатель на структуру
CRITICAL_SECTION
:
VOID WINAPI EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
VOID WINAPI LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
Перед началом "критического" участка кода необходимо вызывать функцию
EnterCriticalSection()
, которая проверяет доступность ресурса. Если ресурс доступен, он блокируется, а код выполняется, иначе — работа потока приостанав- ливается до освобождения ресурса.
В конце "критического" участка кода необходимо объявить критическую секцию доступной при помощи функции
LeaveCriticalSection()
Когда надобность в критической секции отпадет, можно удалить ее функцией
DeleteCriticalSection()
:
VOID WINAPI DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
Рассмотрим простой пример: отдельный поток создается для чтения текстового файла, вывод в окно осуществляется в сообщении
WM_PAINT
(листинг 6.5). Код примера в основном повторяет код, использованный в задаче чтения текстового файла (см. листинг 2.1—2.3).
Листинг 6.5. Критическая секция для двух потоков

#include
#include
#include
#include

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

231
std::vector v;
CRITICAL_SECTION fs; unsigned __stdcall Thread(void* param)
{ std::ifstream in; std::string st;
EnterCriticalSection(&fs); in.open(_T("readme.txt")); while (getline(in, st)) v.push_back(st); in.close();
LeaveCriticalSection(&fs);
InvalidateRect((HWND)param, NULL, TRUE); return 0;
}
///////////////////////////////////////////////////////////////////////
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps; std::vector::iterator it; int y; static HANDLE hThread; switch (message)
{ case WM_CREATE:
InitializeCriticalSection(&fs); hThread = (HANDLE)_beginthreadex(NULL, 0, Thread, hWnd, 0, NULL); break; case WM_COMMAND: switch (LOWORD(wParam))
{ case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam);
} break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps);
EnterCriticalSection(&fs); for (y = 0, it = v.begin(); it < v.end(); ++it, y += 16)
TabbedTextOutA(hdc, 0, y, it->data(), it->length(), 0, NULL, 0);
LeaveCriticalSection(&fs);
EndPaint(hWnd, &ps);

Глава 6

232
break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam);
} return 0;
}
Переменную fs типа
CRITICAL_SECTION
описываем на глобальном уровне, здесь же опишем контейнер vector для типа string
, где будем хранить строки. Инициализацию критической секции проводим в сообщении
WM_CREATE
перед созданием потока.
А в функцию потока и обработчик сообщения
WM_PAINT
вставим в качестве обло- жек пару функций:
EnterCriticalSection(&fs);
LeaveCriticalSection(&fs); что и обеспечит нам последовательность выполнения критического участка кода.
Действительно, если файл еще не прочитан, выводить его в окно рано, в то же вре- мя при перерисовке окна лучше воздержаться от продолжения ввода.
Поскольку в функции потока нам понадобится дескриптор окна hWnd
, передадим его параметром потоковой функции, однако потребуется явное преобразование типа:
InvalidateRect((HWND)param, NULL, TRUE);
Синхронизация с использованием

объектов ядра

Interlocked-функции и критические секции, несмотря на их простоту и скорость ра- боты, не обеспечивают достаточной гибкости для синхронизации потоков. Они не- применимы для синхронизации процессов. В этом случае приходится использовать объекты ядра, которые могут находиться в свободном и занятом состоянии.
Перечислим объекты ядра: процессы, потоки, задания, файлы, события, ожидае-
мые таймеры, семафоры, мьютексы.
При работе с объектами ядра нужно иметь в виду, что они принадлежат не создав- шему их процессу, а операционной системе. Именно это обстоятельство и позволя- ет получить доступ к ним из разных процессов.
Функции для работы с объектами ядра требуют переключения в режим ядра, что отнимает примерно 1000 тактов процессорного времени, поэтому являются "мед- ленными" операциями.
Набор Wait-функций позволяет приостанавливать выполнение потока до освобож- дения объекта ядра. Чаще всего используется функция:
DWORD WINAPI WaitForSingleObject(
HANDLE hHandle,
//дескриптор объекта ядра
DWORD dwMilliseconds);
//время ожидания

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

233
Функция ожидает освобождения объекта ядра dwMilliseconds миллисекунд (зна- чение параметра
INFINITE
используется для неограниченного ожидания) и, если по истечении этого времени объект не был освобожден, возвращает значение
WAIT_TIMEOUT
. При успешном завершении функция возвращает нулевое значение
WAIT_OBJECT_0
Если объект ядра hHandle занят, функция
WaitForSingleObject()
переводит поток в режим ожидания (т. е. поток "засыпает" и не участвует более в распределении процессорного времени), если же объект свободен, он переводится в занятое со- стояние, и выполняются следующие инструкции программного кода.
Имеется еще одна функция, которая позволяет ожидать освобождения сразу не- скольких объектов:
DWORD WINAPI WaitForMultipleObjects(

DWORD dwCount,
//количество объектов ядра
CONST HANDLE* phObjects,
//массив дескрипторов
BOOL fWaitAll,
//режим работы
DWORD dwMilliseconds);
//время ожидания
Если fWaitAll = TRUE
, функция будет ждать освобождения всех объектов, иначе — поток будет возобновлен при освобождении любого объекта из массива phObjects
, а возвращаемое значение определит индекс объекта в массиве phObjects
Семафоры

Семафоры реализованы как счетчики, которые увеличивают свое значение, когда ресурс освобождается, и уменьшают это значение, когда какой-либо поток потре- бует ресурс. Создаются семафоры функцией
CreateSemaphore()
:
HANDLE WINAPI CreateSemaphoreW(

LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //атрибуты доступа
LONG lInitialCount,
//счетчик
LONG lMaximumCount,
//количество задач
LPCWSTR lpName);
//имя семафора
Семафоры являются глобальными системными объектами и, если два различных процесса открывают семафор с одним именем, используют один и тот же объект. Ес- ли вместо имени указан
NULL
, создается локальный для данного процесса семафор.
Параметр lMaximumCount задает максимальное число потоков, которые могут работать с объектом. При значении lMaximumCount = 1
семафор называют исключающим. lInitialCount
— начальное значение счетчика обращений. Если счетчик равен
0
, все функции, ожидающие семафор, будут блокированы, пока значение счетчика не увеличится. Счетчик изменяется в пределах
[0; lMaximumCount]
Функция возвращает дескриптор созданного семафора или
0
в случае ошибки.
Открыть существующий семафор можно функцией
OpenSemaphore()
:
HANDLE WINAPI OpenSemaphoreW(
DWORD dwDesiredAccess, //права доступа, обычно SEMAPHORE_ALL_ACCESS

Глава 6

234
BOOL bInheritHandle,
//если TRUE, дескриптор может быть унаследован
LPCWSTR lpName);
//имя семафора
Если семафор найден, функция возвращает его дескриптор, иначе —
0
Следующая функция освобождает семафор, позволяя использовать его другому процессу или потоку (функция просто увеличивает счетчик семафора на lReleaseCount
).
BOOL WINAPI ReleaseSemaphore(
HANDLE hSemaphore,
//дескриптор семафора
LONG lReleaseCount,
//значение будет добавлено к счетчику
LPLONG lpPreviousCount); //сохраняет предыдущее значение счетчика
Построим простой пример (листинг 6.6), где в диалоговом окне разместим два эле- мента управления Progress Bar (индикатора выполнения) и создадим два потока, каждый из которых управляет своим индикатором. Для наглядности отобразим стандартный светофор.
Работать с семафором можно при помощи функций
WaitForSingleObject()
или
WaitForMultipleObjects()

Каталог: uploads
uploads -> Научно-исследовательская работа Путешествие в мир компьютера Севастьянов Вадим
uploads -> «деревянная игрушка коми пермяцкого народа»
uploads -> Викторина «Я хочу здоровым быть»
uploads -> «чем великобритания интересна для россии?» Великобритания входит в число крупнейших мировых держав
uploads -> Персональные компьютеры, история создания и развития
uploads -> Подросток и компьютерные игры
uploads -> Руководство пользователя 2 Заключение 12
uploads -> Сборник Из опыта проектной деятельности учащихся гимназии №524 в 2012-2013 учебном году Санкт-Петербург 2013


Поделитесь с Вашими друзьями:
1   ...   7   8   9   10   11   12   13   14   15


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал