Модель переменных условий
А теперь давайте объединим все это в едином фрагменте кода, представляющем то, что мы будем называть моделью переменных условий (condition variable model, CV model), которая может существовать в виде сигнальной (signal) и широковещательной (broadcast) моделей. В первых примерах будет использована широковещательная модель. Результат представляет собой программную модель, с которой мы будем работать еще не один раз, и которая может быть использована для решения широкого круга задач синхронизации. Для удобства изложения примеры сформулированы в терминах задачи производителя и потребителя.
Обсуждение может показаться вам несколько абстрактным, однако, поняв суть методики, вы сможете решать многие задачи синхронизации, справиться с которыми без наличия хорошей модели было бы очень трудно.
В упомянутом фрагменте кода имеется несколько ключевых элементов.
• Структура данных типа STATE_TYPE, в которой содержатся все данные, или переменные состояний(state variables), такие как сообщения, контрольные суммы и счетчики, используемые в программе 8.2.
• Мьютекс и одно или более событий, связанных с этой структурой данных и обычно входящих в ее состав.
• Одна или несколько булевых функций, предназначенных для вычисления предикатов переменных условий (condition variable predicates), представляющих собой условия (состояния), наступления которых может ожидать поток. Например, в качестве предикатов могут использоваться следующие условия: "готово новое сообщение", "имеется свободное место в буфере", "очередь не пуста". Можно связывать с каждым предикатом переменной условия отдельное событие, но возможно также использование одного события для представления изменения состояния или же для представления комбинации нескольких предикатов (получаемой посредством применения операции логического "или"). В последнем случае для определения фактического состояния должны проверяться возвращаемые значения отдельных предикативных функций при блокированном мьютексе. Если предикат (логическое выражение) является простым, необходимость в использовании отдельной функции отпадает.
Эти принципы используются потоками производителя и потребителя в приведенном ниже фрагменте кода, включающем единственное событие и предикат переменной условия (реализованный с помощью функции cvp, которая здесь не представлена). В данном примере принимается, что если поток производителя сигнализирует о достижении требуемого состояния, то должны быть освобождены сразу несколько потоков, откуда следует, что сигнал должен рассылаться всем ожидающим потокам потребителя. Так, сигналом, соответствующим созданию потоком производителя нескольких сообщений, может служить увеличение значения счетчика сообщений. Во многих случаях вам может требоваться освобождение только одного потока, что обсуждается после приведенного ниже фрагмента кода.
Этот код ориентирован на работу под управлением Windows 9x и всех версий Windows NT. Для упрощения решения впоследствии в нем будет использована функция SignalObjectAndWait.
Примечание и предостережение
В данном примере намеренно и вполне осознанно используется функция PulseEvent, хотя некоторые авторы, а кое-где и документация Microsoft (см. замечания в соответствующем разделе MSDN), этого делать не рекомендуют. Причины нашего выбора будут ясны из последующего обсуждения и подкреплены примерами, а читателю предлагается решить (корректно) эту задачу, используя функцию SetEvent.
typedef struct _state_t {
HANDLE Guard; /* Мьютекс, защищающий объект. */
HANDLE CvpSet; /* Вручную сбрасываемое событие — выполняется условие, определяемое предикатом cvp(). */
… другие переменные условий …
/* Структура состояния, содержащая счетчики, контрольные суммы и прочее. */
struct STATE_VAR_TYPE StateVar;
} STATE_TYPE State;
…
/* Инициализировать состояние, создавая мьютекс и событие. */
…
/* Поток ПРОИЗВОДИТЕЛЯ, который изменяет состояние. */
WaitForSingleObject(State.Guard, INFINITE);
/* Изменить состояние таким образом, чтобы выполнялось условие, */
/* определяемое предикатом CV. */
/* Пример: к данному моменту подготовлено одно или несколько сообщений.*/
State.StateVar.MsgCount += N;
PulseEvent(State.CvpSet);
ReleaseMutex(State.Guard);
/* Конец интересующей нас части кода потока производителя. */
…
/* Ожидание определенного состояния функцией потока ПОТРЕБИТЕЛЯ. */
WaitForSingleObject(State.Guard, INFINITE);
while (!cvp(&State)) {
ReleaseMutex(State.Guard);
WaitForSingleObject(State.CvpSet, TimeOut);
WaitForSingleObject(State.Guard, INFINITE);
}
/* Теперь этот поток владеет мьютексом, и выполняется условие, */
/* определяемое предикатом cvp(&State). */
/* Предпринять соответствующее действие, возможно, изменяя состояние.*/
…
ReleaseMutex(State.Guard);
/* Конец интересующей нас части кода потока потребителя. */