Синхронизация в многопоточных приложениях MFC - Ожидающие функции
ОГЛАВЛЕНИЕ
Ожидающие функции
Следующая функция - это простейшая ожидающая функция среди многих возможных. Ее объявление имеет такой формат:
DWORD WaitForSingleObject
(
HANDLE hHandle,
DWORD dwMilliseconds
);
Параметр hHandle принимает дескриптор (описатель) объекта, чье сигнализирующее или не сигнализирующее состояние нужно будет проверять. Параметр dwMilliseconds принимает время, которое вызывающий поток должен ждать до тех пор, пока проверяемый объект не перейдет в сигнализирующее состояние. Как только объект перешел в сигнализирующее состояние или заданный временной интервал истек, функция возвращает управление вызывающему потоку. Если dwMilliseconds принимает значение INIFINITE (-1), поток будет ждать до тех пор, пока объект не перейдет в сигнализирующее состояние. Если объект так и не перейдет в сигнализирующее состояние, то поток будет ждать вечно.
Например, данный фрагмент кода проверяет, выполняется процесс [обозначаемый дескриптором hProcess] или нет:
DWORD dw = WaitForSingleObject(hProcess, 0);
switch (dw)
{
case WAIT_OBJECT_0:
// процесс завершился
break;
case WAIT_TIMEOUT:
// процесс еще выполняется
break;
case WAIT_FAILED:
// произошел сбой
break;
}
Как вы можете заметить, мы передали 0 в параметр функции dwMilliseconds, в этом случае функция немедленно проверит состояние объекта [сигнализирующее или не сигнализирующее] и сразу же возвратит управление. Если объект находится в сигнализирующем состоянии, функция вернет WAIT_OBJECT_0. Если состояние объекта не сигнализирующее – функция вернет WAIT_TIMEOUT. В случае сбоя функция вернет WAIT_FAILED (сбой может произойти, если в функцию был передан неправильный дескриптор).
Следующая ожидающая функция подобна предыдущей, за исключением того, что она принимает список дескрипторов и ждет до тех пор, пока хотя бы один из них или все не перейдут в сигнализирующее состояние:
DWORD WaitForMultipleObjects
(
DWORD nCount,
CONST HANDLE *lpHandles,
BOOL fWaitAll,
DWORD dwMilliseconds
);
Параметр nCount принимает число дескрипторов, подлежащих проверке. Параметр lpHandles должен указывать на массив дескрипторов. Если параметр fWaitAll имеет значение TRUE (истина), функция будет ждать, пока все объекты не перейдут в сигнализирующее состояние. Если он имеет значение FALSE (ложь), функция возвратит результат работы и завершится, если хотя бы один объект перейдет в сигнализирующее состояние (при этом неважно, в каком состоянии будут другие объекты). dwMilliseconds имеет такое же значение, что и в предыдущей функции.
Например, следующий фрагмент кода определяет, какой процесс завершится первым из списка дескрипторов процессов HANDLE:
HANDLE h[3];
h[0] = hThread1;
h[1] = hThread2;
h[2] = hThread3;
DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);
switch (dw)
{
case WAIT_FAILED:
// произошел сбой
break;
case WAIT_TIMEOUT:
// ни один процесс не завершился в течение последних 5000мс
break;
case WAIT_OBJECT_0 + 0:
// процесс с дескриптором h[0] завершился
break;
case WAIT_OBJECT_0 + 1:
// процесс с дескриптором h[1] завершился
break;
case WAIT_OBJECT_0 + 2:
// процесс с дескриптором h[2] завершился
break;
}
Как мы видим, функция может возвращать различные значения, которые показывают причину завершения работы функции. Вы уже знаете, что обозначают первые два значения. Следующие значения возвращаются по такой логике; возвращенный WAIT_OBJECT_0 + index показывает, что объект из массива HANDLE, индекс которого равен index, перешел в сигнализирующее состояние. Если параметр fWaitAll равен TRUE (истина), WAIT_OBJECT_0 будет возвращен (если все объекты перешли в сигнализирующее состояние).
Поток переходит из пользовательского режима в режим ядра, если он вызывает ожидающую функцию. Это одновременно хорошо и плохо. Это плохо, потому что для перехода в режим ядра необходимо примерно 1000 циклов процессора, что может быть слишком расточительно в конкретной ситуации. Это хорошо, потому что после перехода в режим ядра не требуется использовать процессор; поток засыпает.
Давайте перейдем к MFC и посмотрим, чем это может нам помочь. Имеются два класса, которые инкапсулируют вызовы ожидающих функций; CSingleLock и CMultiLock. Мы рассмотрим их использование позже в этой статье.
Объект синхронизации | Эквивалентный класс C++ |
События | CEvent |
Критические секции | CCriticalSection |
Взаимоисключающие блокировки | CMutex |
Семафоры | CSemaphore |
Каждый из этих классов является потомком единственного класса – CsyncObject, чьим наиболее часто используемым членом является перегруженный оператор HANDLE, который возвращает основной дескриптор данного объекта синхронизации. Все эти классы объявлены во включаемом файле <AfxMt.h>.