Системное программирование в среде Windows
Шрифт:
Подводя итоги, можно сделать вывод, что комбинация "автоматически сбрасываемое событие/SetEvent" представляет собой сигнальную модель CV, в которой освобождается единственный из ожидающих потоков, а комбинация "вручную сбрасываемое событие/PulseEvent" — широковещательную модель CV, в которой освобождаются все ожидающие потоки. Для потоков Pthreads существуют те же различия, но использование конечных интервалов ожидания событий для широковещательной модели в данном случае не требуется, тогда как в Windows этот фактор является весьма существенным, поскольку освобождение мьютекса и ожидание события не выполняются атомарно, то есть за одну операцию. В то же время, введение функции SignalObjectAndWait
Рассмотрим следующий предикат переменной условия:
В данном случае поток потребителя будет ожидать до тех пор, пока значение счетчика не станет достаточно большим, и поток производителя может увеличивать это значение на произвольную величину. Отсюда, например, становится понятным, как можно реализовать сложные семафоры; вспомните, что обычные семафоры не допускают атомарного выполнения нескольких функций ожидания. В данном же случае поток потребителя может просто уменьшить значение счетчика на К единиц после выхода из цикла, но перед тем, как освободить мьютекс.
Заметьте, что в данном случае подходит широковещательная модель CV, поскольку один поток производителя может увеличить значение счетчика и тем самым разрешить выполнение нескольким, но не всем ожидающим потокам потребителя.
В некоторых случаях уместнее использовать не события, а семафоры, преимущество которых заключается в том, что они позволяют указывать точное количество потоков, которые необходимо освободить. Например, если бы было известно, что каждый из потоков потребителя может получить только одно сообщение, то поток производителя мог бы вызвать функцию ReleaseSemaphore, используя в качестве параметра точное количество сгенерированных сообщений. Однако в общем случае потоку производителя ничего не известно о том, каким образом отдельные потоки потребителя изменят структуру переменной состояния, и поэтому модель переменных условий применима для решения более широкого круга задач.
Модель CV обладает достаточно мощными возможностями, которых хватает для реализации семафоров. Как уже отмечалось ранее, в основе этого метода лежит определение предиката, эквивалентного утверждению: "значение счетчика является ненулевым", и создание структуры состояния, содержащей текущее значение счетчика и его максимально допустимое значение. В упражнении 10.11 представлено завершенное решение, позволяющее манипулировать функциями ожидания путем изменения значений счетчика на несколько единиц одной операцией. Создание семафоров для потоков Pthreads не предусмотрено, поскольку переменные условий предоставляют достаточно широкие возможности.
Использование функции SignalObjectAndWait
Цикл, выполняемый потоком потребителя в предшествующем фрагменте кода, играет очень важную роль в модели CV, поскольку в нем выполняется ожидание изменения состояния, а затем проверяется, является ли состояние именно тем, какое требуется. Последнее условие может не выдерживаться, если событие оказывается слишком обобщенным, указывая, например, только на сам факт изменения состояния, а не на характеристики такого изменения. К тому же, другие потоки могут дополнительно изменить состояние, например, очистить буфер сообщений. Упомянутый цикл требовал выполнения двух функций ожидания и одной функции освобождения мьютекса, как показано ниже.
Использование
Однако в случае Windows NT 5.x (XP, 2000 и Server 2003) и даже Windows NT 4.0 мы можем использовать функцию SignalObjectAndWait — важный элемент усовершенствования, который избавляет от необходимости применения конечных интервалов ожидания и объединяет освобождение мьютекса и ожидание события. При этом кроме явного упрощения программы, производительность в общем случае повышается, что объясняется устранением системного вызова и отсутствием необходимости в настройке длительности интервала ожидания.
Эта функция, при вызове которой используются дескрипторы, указывающие соответственно на мьютекс и событие, упрощает цикл потребителя. Интервал ожидания здесь отсутствует, поскольку вызывающий поток переходит к ожиданию второго дескриптора сразу же после того, как первый дескриптор переходит в сигнальное состояние (что в данном случае означает освобождение мьютекса). Перевод объекта в сигнальное состояние и переход к ожиданию осуществляются атомарным образом, то есть за одну операцию, так что никакой другой поток не может сигнализировать о наступлении события в течение промежутка времени между освобождением мьютекса вызывающим потоком и ожидания потоком события, на которое указывает второй дескриптор. Тогда упрощенный цикл потребителя приобретает следующий вид:
Значением последнего аргумента этой функции, bAlertable, в данном случае является FALSE, однако в последующих разделах, посвященных рассмотрению АРС, он будет полагаться равным TRUE.
Вообще говоря, оба дескриптора могут указывать на любые подходящие объекты синхронизации. В то же время, использовать объект CRITICAL_SECTION в качестве объекта, сигнальное состояние которого отслеживается, нельзя, поскольку допустимыми являются только объекты ядра.
Функция SignalObjectAndWait применяется во всех примерах программ, представленных как в книге, так и на Web-сайте, хотя на Web-сайте находятся и другие варианты решений, о которых будет говориться в тексте. Если программа должна выполняться под управлением Windows 9x, то следует заменить эту функцию парой функций "сигнал/ожидание", как в первоначально приведенном фрагменте кода, и обязательно использовать конечный интервал ожидания.
В разделе, посвященном АРС, представлены различные методы отправки сигналов ожидающим потокам, обеспечивающие получение сигналов только определенными потоками, тогда как в случае событий простых способов, позволяющих контролировать, каким потокам направляются сигналы, не существует.