|
|
|||||||||||||||||||||||||||||
|
Синхронизация процессов при работе с Windows. Waitable timer (таймер ожидания) (исходники)Источник: КомпьютерПресс Анатолий Тенцер
Таймер ожидания отсутствует в Windows 95, и для его использования необходимы Windows 98 или Windows NT 4.0 и выше. Таймер ожидания переходит в сигнальное состояние по завершении заданного интервала времени. Для его создания используется функция CreateWaitableTimer:
Когда параметр bManualReset равен TRUE, то таймер после срабатывания функции ожидания остается в сигнальном состоянии до явного вызова SetWaitableTimer, если FALSE-таймер автоматически переходит в несигнальное состояние. Если lpTimerName совпадает с именем уже существующего в системе таймера, то функция возвращает его идентификатор, позволяя использовать объект для синхронизации между процессами. Имя таймера не должно совпадать с именем уже существующих объектов типов event, semaphore, mutex, job или file-mapping. Идентификатор уже существующего таймера можно получить функцией:
Параметр dwDesiredAccess может принимать следующие значения:
После получения идентификатора таймера поток может задать время его срабатывания функцией SetWaitableTimer:
Рассмотрим параметры подробнее. lpDueTime Задает время срабатывания таймера. Время задается в формате TFileTime и базируется на coordinated universal time (UTC), то есть должно указываться по Гринвичу. Для преобразования системного времени в TFileTime используется функция SystemTimeToFileTime. Если время имеет положительный знак, оно трактуется как абсолютное, если отрицательный - как относительное от момента запуска таймера. lPeriod Задает срок между повторными срабатываниями таймера. Если lPeriod равен 0, то таймер сработает один раз. pfnCompletionRoutine Адрес функции, объявленной как:
Эта функция вызывается, когда срабатывает таймер, если поток, ожидающий его срабатывания, использует функцию ожидания, поддерживающую асинхронный вызов процедур. В функцию передаются три параметра:
Если дополнительная функция обработки не нужна, в качестве этого параметра можно передать NIL. lpArgToCompletionRoutine Это значение передается в функцию pfnCompletionRoutine при ее вызове. fResume Определяет необходимость «пробуждения» системы, если на момент срабатывания таймера она находится в режиме экономии электроэнергии (suspended). Если операционная система не поддерживает пробуждение и fResume равно TRUE, то функция SetWaitableTimer выполнится успешно, однако последующий вызов GetLastError вернет результат ERROR_NOT_SUPPORTED. Если необходимо перевести таймер в неактивное состояние, это можно сделать функцией:
Эта функция не изменяет состояния таймера и не приводит к срабатыванию функций ожидания и вызову процедур-обработчиков. По завершении работы объект должен быть уничтожен функцией CloseHandle. Создадим класс, который ожидает в отдельном потоке наступления заданного времени, а затем вызывает процедуру главного потока приложения. Такой класс может использоваться, например, в планировщике заданий (поскольку таймер ожидания позволяет задавать время срабатывания в абсолютных величинах, отпадает необходимость постоянно анализировать текущее время, используя обычный таймер Windows):
Использовать этот класс можно, например, следующим образом:
Дополнительные объекты синхронизацииНекоторые объекты Win32 API не предназначены исключительно для целей синхронизации, однако могут использоваться с функциями синхронизации. Такими объектами являются: Сообщение об изменении папки (change notification)Windows позволяет организовать слежение за изменениями объектов файловой системы. Для этого служит функция FindFirstChangeNotification:
Параметр dwNotifyFilter - это битовая маска из одного или нескольких следующих значений:
Идентификатор, возвращенный этой функцией, может использоваться в любой функции ожидания. Он переходит в сигнальное состояние, когда в папке происходят изменения, запрошенные для слежения. Можно продолжить слежение, используя функцию FindNextChangeNotification:
По завершении работы идентификатор должен быть закрыт при помощи функции FindCloseChangeNotification:
Чтобы не блокировать исполнение основного потока программы функцией ожидания, удобно реализовать ожидание изменений в отдельном потоке. Реализуем поток на базе класса TThread. Для того чтобы можно было прервать исполнение потока методом Terminate, необходимо, чтобы функция ожидания, реализованная в методе Execute, также прерывалась при вызове Terminate. Для этого будем использовать вместо WaitForSingleObject функцию WaitForMultipleObjects и прерывать ожидание по событию (event), устанавливаемому в Terminate:
Поскольку метод TThread.Terminate не виртуальный, этот класс нельзя использовать с переменной типа TThread, так как в этом случае будет вызываться метод Terminate класса TThread, который не может прервать ожидания, и поток будет выполняться до изменения в папке, за которой ведется слежение. Устройство стандартного ввода с консоли (console input)Идентификатор стандартного устройства ввода с консоли, полученный при помощи вызова функции GetStdHandle(STD_INPUT_HANDLE), можно использовать в функциях ожидания. Он находится в сигнальном состоянии, если очередь ввода консоли не пустая, и в несигнальном - если пустая. Это позволяет организовать ожидание ввода символов или при помощи функции WaitForMultipleObjects совместить его с ожиданием каких-либо других событий. Задание (Job)Job - это новый механизм Windows 2000, позволяющий объединить группу процессов в одно задание и манипулировать ими одновременно. Идентификатор задания находится в сигнальном состоянии, если все процессы, ассоциированные с ним, завершились по причине истечения лимита времени на выполнение задания. Процесс (Process)Идентификатор процесса, полученный при помощи функции CreateProcess, переходит в сигнальное состояние по завершении процесса, что позволяет организовать ожидание завершения процесса, например, при запуске из приложения внешней программы:
Следует понимать, что в этом случае вызывающий процесс будет заморожен полностью и не сможет обрабатывать сообщения. Поэтому, если дочерний процесс может выполняться в течение длительного времени, лучше использовать более корректный вариант ожидания, описанный в разделе, посвященном функции MsgWaitForMultipleObjects. Поток (thread)Идентификатор потока находится в несигнальном состоянии до тех пор, пока поток выполняется. По его завершении идентификатор переходит в сигнальное состояние. Это позволяет легко узнать, завершился ли поток, либо при помощи функции, ожидающей несколько объектов, организовать ожидание завершения одного или всех интересующих потоков. Дополнительные механизмы синхронизацииКритические секцииКритические секции - это механизм, предназначенный для синхронизации потоков внутри одного процесса. Как и мьютекс, критическая секция может в один момент времени принадлежать только одному потоку, однако она предоставляет более быстрый и эффективный механизм, чем мьютексы. Перед использованием критической секции необходимо инициализировать ее функцией:
После создания объекта поток, перед доступом к защищаемому ресурсу, должен вызвать функцию:
Если в этот момент ни один из потоков в процессе не владеет объектом, то поток становится владельцем критической секции и продолжает выполнение. Если секция уже захвачена другим потоком, то выполнение потока, вызвавшего функцию, приостанавливается до ее освобождения. Поток, владеющий критической секцией, может повторно вызывать функцию EnterCriticalSection без блокирования своего исполнения. По завершении работы с защищаемым ресурсом поток должен вызвать функцию LeaveCriticalSection:
Эта функция освобождает объект независимо от количества предыдущих вызовов потоком функции EnterCriticalSection. Если имеются другие потоки, ожидающие освобождения секции, один из них становится ее владельцем и продолжает исполнение. Если поток завершился, не освободив критическую секцию, то ее состояние становится неопределенным, что может вызвать блокировку работы программы. Имеется возможность попытаться захватить объект без замораживания потока. Для этого служит функция TryEnterCriticalSection:
Она проверяет, захвачена ли секция в момент ее вызова. Если да - функция возвращает FALSE, в противном случае - захватывает секцию и возвращает TRUE. По завершении работы с критической секцией она должна быть уничтожена вызовом функции DeleteCriticalSection:
Рассмотрим пример приложения, осуществляющего в нескольких потоках загрузку данных по сети. Глобальные переменные BytesSummary и TimeSummary хранят общее количество загруженных байтов и время загрузки. Эти переменные каждый поток обновляет по мере считывания данных; для предотвращения конфликтов приложение должно защитить общий ресурс при помощи критической секции:
Delphi предоставляет класс, инкапсулирующий функциональность критической секции. Класс объявлен в модуле SyncObjs.pas:
Методы Enter и Leave являются синонимами методов Acquire и Release соответственно и добавлены для лучшей читаемости исходного кода:
Защищенный доступ к переменным (Interlocked Variable Access)Часто возникает необходимость в совершении операций над разделяемыми между потоками 32-разрядными переменными. В целях упрощения решения этой задачи Windows API предоставляет функции для защищенного доступа к ним, не требующие использования дополнительных (и более сложных) механизмов синхронизации. Переменные, используемые в этих функциях, должны быть выровнены на границу 32-разрядного слова. Применительно к Delphi это означает, что если переменная объявлена внутри записи (record), то эта запись не должна быть упакованной (packed) и при ее объявлении должна быть активна директива компилятора {$A+}. Несоблюдение данного требования может привести к возникновению ошибок на многопроцессорных конфигурациях.
Функция увеличивает переменную Addend на 1. Возвращаемое значение зависит от операционной системы:
Функция уменьшает переменную Addend на 1. Возвращаемое значение аналогично функции InterlockedIncrement.
Функция записывает в переменную Target значение Value и возвращает предыдущее значение Target. Следующие функции для выполнения требуют Windows 98 или Windows NT 4.0 и старше.
Функция сравнивает значения Destination и Comperand. Если они совпадают, значение Exchange записывается в Destination. Функция возвращает начальное значение Destination.
Функция добавляет к переменной, на которую указывает Addend, значение Value и возвращает начальное значение Addend. РезюмеМногозадачная и многопоточная среда Win32 предоставляет широкие возможности для написания высокоэффективных приложений. Однако написание приложений, использующих многопоточность и взаимодействующих друг с другом, при неаккуратном программировании может привести к их неверной работе, неоправданной загрузке и даже к блокировке всей операционной системы. Во избежание этого следуйте нижеприведенным рекомендациям:
|
|