Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6259
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

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/).

Соседние файлы в предмете Программирование на C++