
Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009
.pdf
316 Часть II. Приступаем к работе
#include <tchar.h>
#include <StrSafe.h> #include "Resource.h"
///////////////////////////////////////////////////////////////////////////////
///
class CQueue { public:
struct ELEMENT {
int ro_nThreadNum, m_nRequestNum;
// другие элементыданных должны быть определены здесь
};
Typedef ELEMENT* PELEMENT;
private: |
|
|
PELEMENT |
m_pElements; |
// массив элементов, подлежащих обра- |
ботке |
|
|
int |
m_nMaxElements; |
// количество элементов в массиве |
HANDLE |
m_h[2]; |
// описатели мьютекса и семафора |
HANDLE |
&m_hmtxQ; |
// ссылка на m_h[0] |
HANDLE |
&m_hsemNumElements; |
// ссылка на m_h[1] |
|
|
|
public:
CQueue(int nMaxElements); ~CQueue();
BOOL Append(PELEMENT pElement, DWORD dwMilliseconds); BOOL Remove(PELEMENT pElement, DWORD dwMilliseconds);
};
///////////////////////////////////////////////////////////////////////////////
///
CQueue::CQueue(int nMaxElements) : m_hmtxQ(m_h[0]), m_hsemNumElements(m_h[1]) {
m_pElements =
(PELEMENT)HeapAlloc(GetProcessHeap(), 0, sizeof(ELEMENT) * nMaxElements);
m_nMaxElements = nMaxElements;
m_hmtxQ = CreateMutex(NULL, FALSE, NULL);
m_hsemNumElements = CreateSemaphore(NULL, 0, nMaxElements, NULL);
}
///////////////////////////////////////////////////////////////////////////////
///
CQueue::~CQueue() {
CloseHandle(m_hsemNumElements);
CloseHandle(m_hmtxQ);

Глава 9. Синхронизация потоков с использованием объектов ядра.docx 317
HeapFree(GetProcessHeap(), 0, m_pElements);
}
///////////////////////////////////////////////////////////////////////////////
///
BOOL CQueue::Append(PELEMENT pElement, DWORD dwTimeout) {
BOOL fOk = FALSE;
DWORD dw = WaitForSingleObject(n_hmtxQ, dwTimeout);
if (dw == WAIT_OBJECT_O) {
//этот поток получил монопольный доступ к очереди
//увеличиваем число элементов в очереди
LONG lPrevCount;
fOk = ReleaseSemaphore(m_hsemNumElements, 1, &lPrevCount); if (fOk) {
//в очереди еще есть место; добавляем новый элемент nLpElements[lPrevCount] = *pElement;
}else {
//Очередь полностью заполнена; устанавливаем код ошибки
//и сообщаем о неудачном завершении вызова.
SetLastError(ERROR_DATABASE_FULL);
}
//Разрешаем другим потокам обращаться к очереди
ReleaseMutex(m_hmtxQ);
}else {
//Время ожидания истекло; устанавливаем код ошибки
//и сообщаем о неудачном завершении вызова.
SetLastError(ERROR_TIMEOUT);
} |
|
return(fOk); |
// GetLastError сообщит дополнительную инфор- |
мацию |
|
}
///////////////////////////////////////////////////////////////////////////////
///
BOOL CQueue::Remove(PELEMENT pElement, DWORD dwTimeout) {
//Ждем монопольного доступа к очереди
//и появления в ней хотя бы одного элемента.
BOOL fOk = (WaitForMultipleObjects(_countof(m_h), m_h, TRUE, dwTimeout) == WAIT_0BJECT_0);

318 Часть II. Приступаем к работе
if (fOk) {
//в очереди есть элемент; извлекаем его
*pElement = m_pElements[0];
//сдвигаем остальные элементы вниз на одну позицию
MoveMemory(&m_pElements[0], &m_pElements[1], sizeof(ELEMENT) * (m_nMaxElements - 1));
//разрешаем другим потокам обращаться к очереди
ReleaseMutex(m_hmtxQ);
}else {
//Время ожидания истекло; устанавливаем код ошибки
//и сообщаем о неудачном завершении вызова.
SetLastError(ERROR_TIMEOUT);
} |
|
return(fOk); |
//GetLastError сообщит дополнительную информа- |
цию |
|
} |
|
///////////////////////////////////////////////////////////////////////////////
|
/// |
|
CQueue g_q(10); |
// совместно используемая очередь |
|
volatile BOOL g_fShutdown = FALSE; |
// сигнализирует клиентским |
|
|
|
// и серверным потокам, |
|
|
// когда им нужно завершаться; |
HWND g_hwnd; |
// позволяет выяснять состояние |
|
|
|
// клиентских и серверных потоков. |
// описатели и количество всех потоков (клиентских и серверных) |
||
HANDLE g_hTh reads[MAXIMUM_WAIT_OBJECTS]; |
|
|
int |
g_nNumThreads = 0; |
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
///
DWORD WINAPI ClientThread(PVOID pvParam) { int nThreadNum = PtrToUlong(pvParam);
HWND hwndLB = GetDlgItem(g_hwnd, IDC_CLIENTS); int nRequestNum = 0;
while ((PV0ID)1 != InterlockedCompareExchangePointer(
(PV0ID*) &g_fShutdown, (PV0ID)0, (PV0ID)0)) {
// Отслеживаем элемент, обрабатываемый в данное время nRequestNum++;

Глава 9. Синхронизация потоков с использованием объектов ядра.docx 319
TCHAR sz[1024];
CQueue::ELEMENT e = { nThreadNum, nRequestNum };
// пытаемся поместить элемент в очередь if (g_q.Append(&e, 200)) {
// указываем номера потока и запроса
StringCchPrintf(sz, _countof(sz), TEXT("Sending %d:%d"), nThreadNum, nRequestNum);
}else {
//поставить элемент в очередь не удалось
StringCchPrintf(sz, _countof(sz), TEXT("Sending %d:%d (%s)"), nThreadNum, nRequestNum, (GetLastError() == ERROR_TIMEOUT)
? TEXT("timeout") : TEXT("full"));
}
// показываем результат добавления элемента
ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));
Sleep(2500); |
// интервал ожидания до добавления |
|
// следующего элемента |
} |
|
|
|
return(0); |
|
}
///////////////////////////////////////////////////////////////////////////////
///
DWORD WINAPI ServerThread(PVOID pvParam) {
int nThreadNum = PtrToUlong(pvParam);
HWND hwndLB = GetDlgItem(g_hwnd, IDC_SERVERS); while ((PV0ID)1 !=
InterlockedCompareExchangePointer(
(PVOID*) &g_fShutdown, (PVOID)0, (PVOID)0)) {
TCHAR sz[1024];
CQueue::ELEMENT e;
// пытаемся получить элемент из очереди if (g_q.Remove(&e, 5000)) {
//Сообщаем, какой поток обрабатывает этот элемент,
//какой лоток поместил его в очередь и какой он по счету.
StringCchPrintf(sz, _countof(sz), TEXT("%: Processing %d:%d"), nThreadNum, e.m_nThreadNum, e.m_nRequestNum);

320Часть II. Приступаем к работе
//на обработку запроса серверу нужно какое-то время
Sleep(2000 * e.m_nThreadNum);
}else {
//получить элемент из очереди не удалось
StringCchPrintf(sz, _countof(sz), TEXT(“%d: (timeout)”), nThreadNum);
}
// показываем результат обработки элемента
ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz)};
}
return(0);
}
///////////////////////////////////////////////////////////////////////////////
///
BOOL Dlg__OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {
chSETDLGICONS(hwnd, IDI_QUEUE);
g_hwnd = &hwnd; |
Используется клиентскими и серверными потоками |
|
// для уведомления о своем состоянии. |
DWORD dwThreadID; |
|
|
|
//создаем клиентские потоки for (int x = 0; x < 4; x++)
g_hThreads[g_nNumThreads++] =
chBEGINTHREADEX(NULL, 0, CientThread, (PVOID) (INT_PTR) x, 0, &dwThreadID);
//создаем серверные потоки
for (int x = 0; x < 2; x++) gJrThreadsEg_nNumThreads++] =
chBEGINTHREADEX(NULL, 0, ServerThread, (PVOID) (INT_PTR) x, 0, &dwThreadID);
return(TRUE);
}
///////////////////////////////////////////////////////////////////////////////
///
void Dlg_OnCownmnd(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{

Глава 9. Синхронизация потоков с использованием объектов ядра.docx 321
switch (id) { case IDCANCEL:
EndDialog(hwnd, id); break;
}
}
///////////////////////////////////////////////////////////////////////////////
///
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, |
Dlg.OnInitDialog); |
chHANDLE_DLGMSG(hwnd, WM_COMNAND, |
Dlg_OnCommand); |
} |
|
return(FALSE); |
|
}
///////////////////////////////////////////////////////////////////////////////
///
int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) {
DialogBox(hinstExe, MAKEINTRESOURCE(IDD_QUEUE), NULL, Dlg_Proc);
InterlockedExchangePointer(&g_fShutdown, TRUE);
// ждем завершения всех потоков, а затем проводим очистку
WaitForMultipleObjects(g_nNumThreads, g_hThreads, TRUE, INFINITE); while (g_nNumThreads--)
CloseHandle(g.hThreads[g.nNuroThreads]);
return(0);
}
///////////////////////////////// End of File ////////////////////////////////
Сводная таблица объектов, используемых для синхронизации потоков
В следующей таблице суммируются сведения о различных объектах ядра применительно к синхронизации потоков.
322 Часть II. Приступаем к работе
Табл. 9-3. Объекты ядра и синхронизация потоков
|
|
|
Побочный |
|
Объект |
Находится в занятом со- |
Переходит в свободное состоя- |
эффект ус- |
|
стоянии, когда: |
ние, когда: |
пешного |
||
|
||||
|
|
|
ожидания |
|
Процесс |
процесс еще активен |
процесс завершается |
Нет |
|
|
|
(ExitProcess, TerminateProcess) |
|
|
Поток |
поток еще активен |
поток завершается (ExitThread, |
Нет |
|
|
|
TerminateThread) |
|
|
Задание |
время, выделенное заданию, |
время, выделенное заданию, ис- |
Нет |
|
|
еще не истекло |
текло |
|
|
Файл |
выдан запрос на ввод-вывод |
завершено выполнение запроса |
Нет |
|
|
|
на ввод-вывод |
|
|
Консольный |
ввода нет |
ввод есть |
Нет |
|
ввод |
|
|
|
|
Уведомление об |
в файловой системе нет из- |
файловая система обнаруживает |
Сбрасывается |
|
изменении фай- |
менений |
изменения |
в исходное |
|
ла |
|
|
состояние |
|
Событие с авто- |
вызывается ResetEvent, Pul- |
вызывается SetEvent или PulseE- |
Сбрасывается |
|
сбросом |
seEvent или ожидание ус- |
vent |
в исходное |
|
|
пешно завершилось |
|
состояние |
|
Событие со |
вызывается ResetEvent или |
вызывается SetEvent или PulseE- |
Нет |
|
сбросом вруч- |
PulseEvent |
vent |
|
|
ную |
|
|
|
|
Ожидаемый |
вызывается CancelWaitable- |
наступает время срабатывания |
Сбрасывается |
|
таймер с авто- |
Timer или ожидание успеш- |
(SetWaitableTimer) |
в исходное |
|
сбросом |
но завершилось |
|
состояние |
|
Ожидаемый |
вызывается CancelWaitable- |
наступает время срабатывания |
Нет |
|
таймер со сбро- |
Timer |
(SetWaitableTimer) |
|
|
сом вручную |
|
|
|
|
Семафор |
ожидание успешно заверши- |
счетчик > 0 (ReleaseSemaphore) |
Счетчик |
|
|
лось |
|
уменьшается |
|
|
|
|
на 1 |
|
Мьютекс |
ожидание успешно заверши- |
поток освобождает мьютекс |
Передается |
|
|
лось |
{ReleaseMutex) |
потоку во |
|
|
|
|
владение |
|
Критическая |
ожидание успешно заверши- |
поток освобождает критическую |
Передается |
|
секция (пользо- |
лось |
секцию (LeaveCriticalSection) |
потоку во |
|
вательского ре- |
((Try)EnterCriticalSection) |
|
владение |
|
жима) |
|
|
|
Глава 9. Синхронизация потоков с использованием объектов ядра.docx 323
Табл. 9-3. (окончание)
|
|
|
Побочный |
|
Объект |
Находится в занятом со- |
Переходит в свободное состоя- |
эффект ус- |
|
стоянии, когда: |
ние, когда: |
пешного |
||
|
||||
|
|
|
ожидания |
|
SRWLock |
ожидание успешно заверши- |
поток освобождает SRWLock |
Передается |
|
(пользователь- |
лось (AcquireSRWLock (Ex- |
(ReleaseSRWLock (Exclusive)) |
потоку во |
|
ского режима) |
clusive)) |
|
владение |
|
Условная пере- |
ожидание успешно заверши- |
поток пробуждается |
Нет |
|
менная (пользо- |
лось (SleepConditionVariable) |
(Wake(All)ConditionVariable) |
|
|
вательского ре- |
|
|
|
|
жима) |
|
|
|
Interlocked-функции (пользовательского режима) никогда не приводят к исключению потока из числа планируемых; они лишь изменяют какое-то значение и тут же возвращают управление.
Другие функции, применяемые в синхронизации потоков
При синхронизации потоков чаще всего используются функции WaitForSingleObject и WaitForMultipleObjects. Однако в Windows есть и другие, несколько отличающиеся функции, которые можно применять с той же целью. Если вы понимае-
те, как работают Wait-ForSingleObject и WaitForMultipleObjects, вы без труда раз-
беретесь и в этих функциях.
Асинхронный ввод-вывод на устройствах
При асинхронном вводе-выводе поток начинает операцию чтения или записи и не ждет ее окончания. Например, если потоку нужно загрузить в память большой файл, он может «попросить» систему сделать это за него. И пока система грузит файл в память, поток спокойно занимается другими задачами — создает окна, инициализирует внутренние структуры данных и т. д. Закончив, поток приостанавливает себя и ждет уведомления от системы о том, что загрузка файла завершена.
Объекты устройств являются синхронизируемыми объектами ядра, а это означает, что вы можете вызывать WaitForSingleObject и передавать ей описатель ка- кого-либо файла, сокета, коммуникационного порта и т.д. Пока система выполняет асинхронный ввод-вывод, объект устройства пребывает в занятом состоянии. Как только операция заканчивается, система переводит объект в свободное состояние, и поток узнает о завершении операции. С этого момента поток возобновляет выполнение.
Функция WaitForlnputldle
Поток может приостановить себя и вызовом WaitForInputIdle:
324 Часть II. Приступаем к работе
DWORD WaitForInputIdle(
HANDLE hProcess,
DWORD dwMilliseconds);
Эта функция ждет, пока у процесса, идентифицируемого описателем hProcess, не опустеет очередь ввода в потоке, создавшем первое окно приложения. WaitForInputIdle полезна для применения, например, в родительском процессе, который порождает дочерний для выполнения какой-либо нужной ему работы. Когда один из потоков родительского процесса вызывает CreateProcess, он продолжает выполнение и в то время, пока дочерний процесс инициализируется. Этому потоку может понадобиться описатель окна, создаваемого дочерним процессом. Единственная возможность узнать о моменте окончания инициализации дочернего процесса — дождаться, когда тот прекратит обработку любого ввода. Поэтому после вызова CreateProcess поток родительского процесса должен вызвать WaitForInputldle.
Эту функцию можно применить и в том случае, когда вы хотите имитировать в программе нажатие каких-либо клавиш. Допустим, вы асинхронно отправили в главное окно приложения следующие сообщения:
WM_KEYDOWN с виртуальной клавишей VK_MENU
WM_KEYDOWN с виртуальной клавишей VK_F
WM_KEYUP с виртуальной клавишей VK_F
WM_KEYUP с виртуальной клавишей VK_MENU
WM_KEYDOWN с виртуальной клавишей VK_O
WM_KEYUP с виртуальной клавишей VK_O
Эта последовательность дает тот же эффект, что и нажатие клавиш Alt+F, О, — в большинстве англоязычных приложений это вызывает команду Open из меню File. Выбор данной команды открывает диалоговое окно; но, прежде чем оно появится на экране, Windows должна загрузить шаблон диалогового окна из файла и «пройтись» по всем элементам управления в шаблоне, вызывая для каждого из них функцию CreateWindow. Разумеется, на это уходит какое-то время. Поэтому приложение, асинхронно отправившее сообщения типа WM_KEY*, теперь может вызвать WaitForInputIdle и таким образом перейти в режим ожидания до того момента, как Windows закончит создание диалогового окна и оно будет готово к приему данных от пользователя. Далее программа может передать диалоговому окну и его элементам управления сообщения о еще каких-то клавишах, что заставит диалоговое окно проделать те или иные операции.
С этой проблемой, кстати, сталкивались многие разработчики приложений для 16-разрядной Windows. Программам нужно было асинхронно передавать сообщения в окно, но получить точной информации о том, создано ли это окно и готово ли к работе, они не могли. Функция WaitForInputIdle решает эту проблему.
Глава 9. Синхронизация потоков с использованием объектов ядра.docx 325
Функция MsgWaitForMultipleObjects(Ex)
При вызове MsgWaitForMultipleObjects или MsgWaitForMultipleObjectsEx поток переходит в ожидание своих (предназначенных этому потоку) сообщений:
DWORD MsgWaitForMultipleObjects(
DWORD dwCount,
PHANDLE phObjects,
BOOL bWaitAll,
DWORD dwMilliseconds,
DWORD dwWakeMask);
DWORD MsgWaitForMultipleObjectsEx(
DW0RD dwCount,
PHANDLE phObjects,
DWORD dwMilliseconds,
DWORD dwWakeMask,
DWORD dwFlags);
Эти функции аналогичны WaitForMultipleObjects. Единственное различие заключается в том, что они пробуждают поток, когда освобождается некий объект ядра или когда определенное оконное сообщение требует перенаправления в окно, созданное вызывающим потоком.
Поток, который создает окна и выполняет другие операции, относящиеся к пользовательскому интерфейсу, должен работать с функцией MsgWaitForMultipleObjectsEx, а не с WaitForMultipleObjects, так как последняя не дает возможности реагировать на действия пользователя.
Функция WaitForDebugEvent
В Windows встроены богатейшие отладочные средства. Начиная исполнение, отладчик подключает себя к отлаживаемой программе, а потом просто ждет, когда операционная система уведомит его о каком-нибудь событии отладки, связанном с этой программой. Ожидание таких событий осуществляется через вызов:
BOOL WaitForDebugEvent(
PDEBUG_EVENT pde,
DWORD dwMilliseconds);
Когда отладчик вызывает WaitForDebugEvent, его ноток приостанавливается. Система уведомит поток о событии отладки, разрешив функции WaitForDebugEvent вернуть управление. Структура, на которую указывает параметр pde, заполняется системой перед пробуждением потока отладчика. В ней содержится информация, касающаяся только что произошедшего события отладки. Подробнее о написании собственных отладчиков см. в статье «Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities, Part 2» в MSDN Magazine (http://msdn.microsoft.com/msdnmag/issues/02/08/EscapefromDLLHell/).