Синхронизация в многопоточных приложениях MFC - События
ОГЛАВЛЕНИЕ
События
Обычно события используются в случаях, когда поток (или потоки) должны начать выполняться после того, как произошло определенное событие. Например, поток может ждать до тех пор, пока не будут собраны необходимые данные, и затем начать сохранение данных на жестком диске. Есть два типа событий: с ручным сбросом и с автоматическим сбросом. С помощью использования события мы легко можем сообщить другому потоку, что определенное действие имело место. С помощью события первого типа - это ручной сброс, поток может уведомить более чем один поток об определенном действии. Но с помощью события второго типа, а именно при помощи события с автоматическим сбросом, можно уведомить только один поток. В MFC есть класс CEvent, который инкапсулирует объект события (в терминах Windows, он представлен значением HANDLE). Конструктор CEvent позволяет нам создавать события с ручным сбросом и с автоматическим сбросом. По умолчанию создается второй тип события. Чтобы уведомить ожидающие потоки, мы должны использовать метод CEvent::SetEvent, это означает, что такой тип вызова заставит событие перейти в сигнализирующее состояние. Если это событие с ручным сбросом, то оно будет оставаться в сигнализирующем состоянии до тех пор, пока не будет вызвана соответствующая процедура CEvent::ResetEvent , которая заставит событие перейти в не сигнализирующее состояние. Это функция, которая позволяет потоку уведомить более чем один поток с помощью единственного вызова функции SetEvent. Если событие с автоматическим сбросом, то только один поток из всех ожидающих потоков сможет получить сообщение. После того как поток получит сообщение, событие автоматически перейдет в не сигнализирующее состояние. Следующие два примера иллюстрируют эту теорию. Первый пример:
// создать событие с автоматическим сбросом
CEvent g_eventStart;
UINT ThreadProc1(LPVOID pParam)
{
::WaitForSingleObject(g_eventStart, INFINITE);
...
return 0;
}
UINT ThreadProc2(LPVOID pParam)
{
::WaitForSingleObject(g_eventStart, INFINITE);
...
return 0;
}
В этом коде создается глобальный объект CEvent с автоматическим сбросом. В дополнение к этому, есть два работающих потока, которые ожидают событие, которое сообщит им о том, что нужно начать выполняться. Как только третий поток вызовет SetEvent для того объекта, один и только один поток из этих двух потоков (заметьте, что никто не говорит, какой конкретно поток) получит сообщение, и после этого событие перейдет в не сигнализирующее состояние, которое не позволит второму потоку захватить событие. Код, хотя и не очень полезный, показывает, как работает событие с автоматическим сбросом. Давайте посмотрим на второй пример:
// создать событие с ручным сбросом
CEvent g_eventStart(FALSE, TRUE);
UINT ThreadProc1(LPVOID pParam)
{
::WaitForSingleObject(g_eventStart, INFINITE);
...
return 0;
}
UINT ThreadProc2(LPVOID pParam)
{
::WaitForSingleObject(g_eventStart, INFINITE);
...
return 0;
}
Этот код отличается от предыдущего только параметром конструктора CEvent. Но в смысле функциональности, есть принципиальное различие в способе выполнения двух потоков. Если третий поток вызывает метод SetEvent для этого объекта, то будет возможно гарантировать, что два потока начнут выполняться одновременно (почти одновременно). Это происходит, потому что событие с ручным сбросом после перехода в сигнализирующее состояние не перейдет в не сигнализирующее состояние до тех пор, пока соответствующий метод ResetEvent не закончит работу.
Есть и другой метод для работы с этими событиями - CEvent::PulseEvent. Этот метод сначала заставляет событие перейти в сигнализирующее состояние и затем заставляет вернуться обратно в не сигнализирующее состояние. Если событие с ручным сбросом, при переходе события в сигнализирующее состояние все ожидающие потоки будут уведомлены, и затем событие перейдет в не сигнализирующее состояние. Если событие с автоматическим сбросом, то только один поток будет уведомлен, даже если есть много ожидающих потоков. Если отсутствуют ожидающие потоки, то вызов ResetEvent не будет ничего делать.