Харт Джонсон М.
Шрифт:
Выход из объекта CS, которым данный поток не владеет, может привести к непредсказуемым результатам, включая блокирование самого потока.
Для возврата из функции EnterCriticalSection не существует конечного интервала ожидания; другие потоки будут блокированы на неопределенное время, пока поток, владеющий объектом CS, не покинет его. Однако, используя функцию TryEnterCriticalSection, можно тестировать (опросить) CS, чтобы проверить, не владеет ли им другой поток.
Возврат функцией TryEnterCriticalSection значения True означает, что вызывающий поток приобрел права владения критическим участком кода, тогда как возврат значения False говорит о том, что данный критический участок кода уже принадлежит другого потока.
Объекты CRITICAL_SECTION обладают тем преимуществом, что они не являются объектами ядра и поддерживаются в пользовательском пространстве. Обычно, но не всегда, это приводит к дополнительному улучшению показателей производительности. К обсуждению аспектов производительности мы вернемся после того, как ознакомимся с объектами синхронизации, относящимися к ядру.
Настройка спин-счетчика
Обычно, если в результате выполнения функции EnterCriticalSection поток обнаруживает, что объект CS уже принадлежит другому потоку, он входит в ядро и остается блокированным до тех пор, пока не освободится объект CRITICAL_SECTION, что требует определенного времени. Однако в SMP-системах вы можете потребовать, чтобы поток повторил попытку завладеть объектом CS, прежде чем блокироваться, поскольку существует вероятность того, что поток, владеющий CS, выполняется на другом процессоре и в любой момент может освободить CS. Это может оказаться полезным для повышения производительности, если между потоками наблюдается высокая состязательность за право владения единственным объектом CRITICAL_SECTION. Влияние упомянутых факторов на производительность обсуждается далее в этой и последующих главах.
Для настройки счетчика занятости, или спин-счетчика (spin-count), предназначены две функции, одна из которых, SetCriticalSectionSpinCount, обеспечивает динамическую настройку счетчика, а вторая, InitializeCritical-SectionAndSpinCount, выступает в качестве замены функции Initialize-CriticalSection. Настройка спин-счетчика рассматривается в главе 9.
Использование объектов CRITICAL_SECTION для защиты разделяемыхпеременных
Использование объектов CRITICAL_SECTION не вызывает сложностей, и одним из наиболее распространенных способов их применения является обеспечение доступа потоков к разделяемым глобальным переменным. Рассмотрим, например, многопоточный сервер (аналогичный представленному на рис. 7.1), в котором необходимо вести учет следующих статистических данных:
• Общее количество полученных запросов.
• Общее количество отправленных ответов.
• Количество запросов, обрабатываемых в настоящее время всеми потоками сервера.
Поскольку переменные счетчиков являются глобальными переменными процесса, нельзя допустить того, чтобы одновременно два потока изменяли их значения. Один из методов обеспечения этого, базирующийся на применении объектов CRITICAL_SECTION, иллюстрирует схема, показанная ниже на рис. 8.2. Использование объектов CRITICAL_SECTION демонстрируется на примере программы 8.1, представляющей намного более простую систему, чем серверная.
Объекты CS могут привлекаться для решения задач, аналогичных той, которую иллюстрирует рис. 8.1, где два потока увеличивают значение одной и той же переменной. Приведенный ниже фрагмент кода обеспечивает нечто большее, нежели простое увеличение переменной, поскольку для этого достаточно было бы воспользоваться функциями взаимоблокировки. Обратите внимание на спецификатор volatile, предотвращающий размещение текущего значения переменной оптимизирующим компилятором в регистре, а не в ячейке памяти, отведенной для хранения переменной. Кроме того, в этом примере используется промежуточная переменная; этот необязательный элемент снижает эффективность программы, однако позволяет более отчетливо продемонстрировать, каким образом решается задача, иллюстрируемая рис. 8.1.
На рис. 8.2 представлена одна из возможных последовательностей выполнения программы для случая, изображенного на рис. 8.1, и продемонстрировано, каким образом объекты CS упрощают решение проблемы синхронизации.
Программа 8.1 демонстрирует, насколько полезными могут быть объекты CS.
Пример: простая система "производитель/потребитель"
Программа 8.1 иллюстрирует, насколько полезными могут быть объекты CS. Кроме того, эта программа демонстрирует, как создаются защищенные структуры данных для хранения состояний объектов, и знакомит с понятием инварианта (invariant) — свойства состояния объекта, относительно которого гарантируется (путем соответствующей реализации программы), что оно будет истинным за пределами критического участка кода.