Страница 10 из 15
События События (Event), также как и мьютексы имеют два состояния - установленное и сброшенное. События бывают со сбросом вручную и с автосбросом. Когда поток дождался (wait-функция вернула управление) события с автосбросом, такое событие автоматически сбрасывается. В противном случае событие нужно сбрасывать вручную, вызвав функцию ResetEvent(). Допустим, сразу несколько потоков ожидают одного и того же события, и событие сработало. Если это было событие с автосбросом, то оно позволит работать только одному потоку (ведь сразу же после возврата из его wait-функции событие сбросится автоматически!), а остальные потоки останутся ждать. Если же это было событие со сбросом вручную, то все потоки получат управление, а событие так и останется в установленном состоянии, пока какой-нибудь поток не вызовет ResetEvent().
Пример 5. Вот еще один пример многопоточного приложения. Программа имеет два потока; один готовит данные, а второй отсылает их на сервер. Разумно распараллелить их работу. Здесь потоки должны работать по очереди. Сначала первый поток готовит порцию данных. Потом второй поток отправляет ее, а первый тем временем готовит следующую порцию и т.д. Для такой синхронизации понадобится два event'а с автосбросом. |
unsigned __stdcall CaptureThreadFunc( void * arg) // Поток, готовящий данные { while (bSomeCondition) { WaitForSingleObject(m_hEventForCaptureTh,INFINITE); // Ждем своего события ... // Готовим данные SetEvent(hEventForTransmitTh); // Разрешаем работать второму потоку } _endthreadex( 0 ); return 0; };
unsigned __stdcall TransmitThreadFunc( void * arg) // Поток, отсылающий данные. { while (bSomeCondition) { WaitForSingleObject(m_hEventForTransmitTh,INFINITE); // Ждем своего события ... // Данные готовы, формируем из них пакет для отправки SetEvent(hEventForCaptureTh); // Разрешаем работать первому потоку, а сами... ... // отправляем пакет } _endthreadex( 0 ); return 0; };
int main(int argc, char* argv[]) // Основной поток { // Создаем два события с автосбросом, со сброшенным начальным состоянием hEventForCaptureTh = CreateEvent(NULL,FALSE,FALSE,NULL); hEventForTransmitTh = CreateEvent(NULL,FALSE,FALSE,NULL);
// Создаем потоки hCaptureTh = (HANDLE)_beginthreadex( NULL, 0, &CaptureThreadFunc, 0, 0,&uTh1); hTransmitTh = (HANDLE)_beginthreadex( NULL, 0, &TransmitThreadFunc, 0, 0,&uTh2); // Запускаем первый поток SetEvent(hEventForCaptureTh);
.... }
|
Пример 6. Другой пример. Программа непрерывно в цикле производит какие-то вычисления. Нужно иметь возможность приостановить на время ее работу. Допустим, это просмотрщик видео файлов, который в цикле, кадр за кадром отображает информацию на экран. Не будем вдаваться в подробности видео функций. Реализуем функции Pause и Play для программы. Используем событие со сбросом вручную. |
// Главная функция потока, которая в цикле отображает кадры unsigned __stdcall VideoThreadFunc( void * arg) { while (bSomeCondition) { WaitForSingleObject(m_hPauseEvent,INFINITE); // Если событие сброшено, ждем ... // Отображаем очередной кадр на экран } _endthreadex( 0 ); return 0; };
void Play() { SetEvent(m_hPauseEvent); };
void Pause() { ResetEvent(m_hPauseEvent); };
| Функция PulseEvent() устанавливает событие и тут же переводит его обратно в сброшенное состояние; ее вызов равнозначен последовательному вызову SetEvent() и ResetEvent(). Если PulseEvent вызывается для события со сбросом в ручную, то все потоки, ожидающие этот объект, получают управление. При вызове PulseEvent для события с автосбросом пробуждается только один из ждущих потоков. А если ни один из потоков не ждет объект-событие, вызов функции не дает никакого эффекта.
Пример 7. Реализуем функцию NextFrame() для предыдущего примера для промотки файла вручную по кадрам. |
void NextFrame() { PulseEvent(m_hPauseEvent); };
| |