Объекты синхронизации и функции ожидания 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Объекты синхронизации и функции ожидания



Объекты синхронизации представляют собой частный случай объектов ядра Windows. Отличительной особенностью объектов синхронизации является возможность использовать в качестве аргументов функций ожидания.

Для создания объекта нужно вызвать функцию CreateXxx, где вместо Xxx указывается название конкретного типа объектов, например, CreateMutex или CreateEvent.

Общей чертой всех объектов синхронизации является наличие двух состояний, одно из которых называется сигнальным (signaled), а другое, соответственно, несигнальным. Смысл сигнального состояния различен для разных типов объектов, общим же является то, что состояние ожидания для нити заканчивается тогда, когда объект переходит в сигнальное состояние.

Для конкретных типов объектов вместо термина «сигнальное состояние» может быть удобно использовать как синонимы «свободно», «включено», «имеется», «произошло» и т.п., а для несигнального, соответственно, «занято», «выключено», «нет» и т.п.

Поскольку объекты существуют в системной памяти, процессы могут получить к ним доступ только с помощью хэндлов. Это требует затрат времени на переключение контекста памяти при работе с объектами, но зато дает возможность синхронизации нитей, принадлежащих разным процессам. Для этого при создании объектов ядра одним из параметров функции CreateXxx может указываться имя этого объекта. Если после этого другой процесс попытается создать объект того же типа и с тем же именем, то он получит хэндл уже существующего объекта. В результате оба процесса будут работать с одним и тем же объектом синхронизации.

К функциям ожидания относятся WaitForSingleObject (ожидание одного объекта) и WaitForMultipleObjects (ожидание нескольких объектов). Рассмотрим сначала первую из них.

Функция WaitForSingleObject имеет два аргумента:

· хэндл какого-либо объекта синхронизации;

· тайм-аут ожидания, т.е. максимальное время ожидания (в миллисекундах).

Эта функция – блокирующая: если в момент ее вызова указанный объект находится в несигнальном состоянии, то вызвавшая нить переводится в состояние ожидания (разумеется, пассивного!). Ожидание, как правило, завершается в одном из двух случаев: либо объект переходит в сигнальное состояние, либо истекает заданный тайм-аут. Третий, особый случай завершения связан с понятием «заброшенный мьютекс», которое будет пояснено ниже. Значение, возвращаемое функцией, позволяет узнать, какая из трех причин пробуждения нити имела место.

Если в момент вызова функции указанный объект уже находился в сигнальном состоянии, то ожидания не происходит, функция сразу же возвращает результат.

Если задана нулевая величина тайм-аута, то выполнение функции сводится к проверке, находится ли в данный момент объект в сигнальном состоянии.

Если значение тайм-аута равно константе INFINITE, то время ожидания не ограничено, т.е. нить может пробудиться только по сигнальному состоянию объекта.

Функция WaitForMultipleObjects отличается тем, что вместо единственного хэндла принимает в качестве аргументов адрес массива, содержащего несколько хэндлов, и количество этих хэндлов (размер массива). Как и в предыдущем случае, задается величина тайм-аута. Дополнительный, четвертый аргумент – булевский флаг режима ожидания. Значение FALSE означает ожидание любого объекта (достаточно, чтобы хоть один из них перешел в сигнальное состояние), TRUE – ожидание всех объектов (ожидание завершится, только когда все объекты окажутся в сигнальном состоянии).

Возвращаемое значение позволяет определить причину пробуждения:

· переход одного из заданных объектов в сигнальное состояние, и какого именно объекта;

· переход одного из объектов-мьютексов в «заброшенное» состояние (будет пояснено ниже), и какого именно объекта;

· истечение тайм-аута ожидания.

Кроме двух названных, имеется еще ряд функций ожидания. Функции WaitForSingleObjectEx и WaitForMultipleObjectsEx позволяют также дожидаться завершения асинхронных операций ввода/вывода для указанного файла. Функция MsgWaitForMultipleObjects позволяет, наряду с объектами синхронизации, использовать для пробуждения появление сообщений, ожидающих обработки. Функция SignalObjectAndWait выполняет редкостный трюк: она переводит один объект в сигнальное состояние и тут же начинает дожидаться другого объекта.

Типы объектов синхронизации

Список объектов синхронизации включает следующие типы объектов:

· процессы;

· нити;

· события (в узком смысле);

· мьютексы (двоичные семафоры);

· семафоры (недвоичные);

· таймеры;

· уведомления об изменениях в каталоге;

· консольный ввод.

Рассмотрение объектов синхронизации проще всего начать с процессов и нитей. Для тех и других сигнальным состоянием является только состояние завершения работы. Другими словами, можно заставить нить одного процесса ожидать завершения работы другой нити или процесса.

Простейшим из объектов, предназначенных только для синхронизации, является событие (event). Он создается при помощи функции CreateEvent, которая требует указать вид события (с автосбросом или с ручным сбросом), а также его начальное состояние – сигнальное или несигнальное. Переход в сигнальное состояние выполняется функцией SetEvent, а в несигнальное – ResetEvent. Функция PulseEvent как бы совмещает обе предыдущие: сигнальное состояние включается, функция ожидания, если она была вызвана ранее, завершается, но сигнальное состояние тут же сбрасывается.

Событие с автосбросом означает, что функция ожидания при завершении ожидания сбрасывает то событие (или события), которого (которых) она дождалась. Если несколько нитей ждут одного и того же события с автосбросом, то разблокирована будет только одна из них (Windows не гарантирует, какая именно), поскольку остальным «события не достанется». Напротив, событие с ручным сбросом остается в сигнальном состоянии и может быть сброшено только функцией ResetEvent, а до тех пор оно может «накормить» сколько угодно ожидающих нитей. Можно сказать, что событие с ручным сбросом больше похоже не на событие, а на состояние.

Следующий тип объектов – мьютекс (mutex). Это почти в чистом виде двоичный семафор в смысле Дейкстры. Мьютекс может быть либо свободен (это его сигнальное состояние), либо захвачен какой-либо нитью. При создании мьютекса в функции CreateMutex указывается его начальное состояние. Допускается многократный захват мьютекса одной и той же нитью, но после этого нить должна столько же раз освободить мьютекс, чтобы он вернулся в свободное состояние.

Для освобождения мьютекса нить, владеющая им, вызывает функцию ReleaseMutex, которая является аналогом дейкстровской V(S). Никакая другая нить, кроме владельца, не может освободить «чужой» мьютекс.

В специальной функции для захвата мьютекса нет необходимости, поскольку работу примитива P(S) с успехом выполняют функции ожидания.

Если нить, владеющая мьютексом, завершает свою работу, не освободив мьютекс, то он переходит в «заброшенное» состояние: теперь уже никто не может его освободить. Такую ситуацию следует считать признаком неаккуратного программирования, и она может быть признаком наличия в программе более серьезных ошибок синхронизации.

Объект типа семафор (semaphore) представляет собой недвоичный семафор, используемый обычно для управления распределением ограниченного числа единиц ресурса. В функции CreateSemaphore указываются два числа: максимальное значение счетчика, связанного с семафором (сколько всего имеется единиц ресурса), и начальное значение этого счетчика (сколько единиц свободно). Сигнальным является состояние семафора со счетчиком больше нуля. Функция ожидания уменьшает счетчик на 1, а функция ReleaseSemaphore увеличивает его на заданное количество единиц, но так, чтобы результат не превышал максимального значения.

В отличие от мьютекса, семафор не имеет владельца, т.е. возможна и вполне обычна ситуация, когда одна нить захватывает единицы ресурса, а другая освобождает их.

Таймер «для ожидания» (waitable timer) создается с помощью функции CreateWaitableTimer, после чего должен еще быть установлен функцией SetWaitableTimer. При создании таймера указывается его вид: с ручным сбросом или с автосбросом, аналогично объекту «событие». При вызове SetWaitableTimer указывается время до перехода в сигнальное состояние, в единицах по 100 нс. Время можно указать либо абсолютно (когда «зазвонить»), либо относительно текущего момента (как долго ждать). Можно также указать период (в миллисекундах), через который должен повторяться сигнал.

Таймер с ручным сбросом, перейдя в сигнальное состояние, остается в нем до вызова функции CancelWaitableTimer или повторного вызова SetWaitableTimer. Таймер с автосбросом может быть также сброшен функциями ожидания.

Полезно напомнить, что в Windows широко используется и другой тип таймеров «для сообщений», создаваемый функцией SetTimer, но эти таймеры не могут взаимодействовать с функциями ожидания и даже не являются объектами.

Объект типа уведомление об изменениях создается при помощи функции FindFirstChangeNotification. При этом указываются следующие аргументы:

· каталог файловой системы, изменения в котором должны отслеживаться;

· флаг, указывающий, должны ли отслеживаться изменения только в заданном каталоге или также во всех вложенных подкаталогах;

· фильтр отслеживаемых изменений.

Фильтр изменений представляет собой маску, в которой отдельные биты задают такие события, как изменение списка имен файлов и каталогов (это включает их создание, удаление и переименование), изменение атрибутов файла, размера, даты изменения и атрибутов защиты.

Функция возвращает хэндл объекта, который переходит в сигнальное состояние один раз, при первом появлении одного из включенных в фильтр изменений. Если требуется повторять отслеживание, следует вызывать функцию FindNextChangeNotification, передавая ей хэндл. Для удаления объекта нужно вызвать функцию с диким именем FindCloseChangeNotification.

Уведомления об изменениях позволяют избежать активного ожидания для программ, которые должны отслеживать и отображать состояние каталогов в динамике (как это делают, например, Проводник или Far Manager). При этом характер происшедшего изменения программа может выяснить, только перечитав каталог, но зато достоверно известно, что какое-то изменение было.

Хэндл объекта типа консольный ввод возвращается функцией CreateFile, у которой вместо имени открываемого файла указано устройство консольного ввода (CONIN$). Объект в сигнальном состоянии, когда в буфере ввода есть непрочитанные символы.

Критические секции

Еще одним средством синхронизации потоков служат переменные типа CRITICAL_SECTION. По назначению они совпадают с рассмотренными выше мьютексами, однако реализация двоичного семафора в этом случае совершенно иная. Если мьютексы – это один из типов объектов ядра, то критические секции – просто переменные. В чем здесь принципиальная разница? В том, что объекты существуют в памяти системы, поэтому прикладная программа не может напрямую обратиться к объекту, прочитать или записать его значение. Программа может только получить хэндл объекта, а затем она поручает системе выполнить то или иное действие с объектом, на который указывает данный хэндл. Такое решение позволяет использовать один объект для связи нескольких процессов, сохраняя при этом изоляцию памяти процесса от памяти системы и других процессов.

Недостаток использования объектов ядра в том, что обращение к ним всякий раз требует переключения контекста с пользовательского на системный и обратно, а это требует времени. Такова цена изоляции процессов.

Если требуется синхронизировать работу нитей одного и того же процесса, то более «дешевым» решением может быть использование переменных CRITICAL_SECTION. Программист должен описать переменную этого типа в программе процесса, при этом для всех нитей процесса будет доступен адрес этой переменной. Перед началом работы с критической секцией одна из нитей процесса должна вызвать функцию InitializeCriticalSection, передав ей адрес переменной. Затем он может использовать функцию EnterCriticalSection, чтобы занять двоичный семафор, и функцию LeaveCriticalSection, чтобы освободить его. Как и для мьютекса, один и тот же поток может несколько раз занять критическую секцию, но потом должен столько же раз освободить ее. Имеется еще удобная функция TryEnterCriticalSection, которая позволяет избежать блокировки и занимает критическую секцию, только если та была свободна в момент обращения. По истечении необходимости в синхронизации нитей следует вызвать функцию DeleteCriticalSection.

Вызов всех перечисленных функций выполняется системой гораздо быстрее, чем вызов функций, работающих с хэндлами объектов, поскольку вся работа выполняется в памяти вызывающего процесса, без переключения контекста памяти. Но зато невозможно использовать критические секции для синхронизации нитей разных потоков, для этого нужны мьютексы.

Сообщения

Огромную роль в организации работы прикладных программ и самой системы Windows играют сообщения (messages). На обработке сообщений, посылаемых системой, основана вся работа с графическим интерфейсом программ. Процессы также могут обмениваться сообщениями с целью синхронизации действий и обмена данными.

Сообщение представляет собой структуру данных, содержащую тип сообщения, адресат сообщения (конкретное окно, все окна или нить процесса), время посылки сообщения, координаты курсора (для сообщений от мыши), а также два параметра, смысл которых зависит от типа сообщения.

Система посылает сообщения приложениям по самым разным поводам: при нажатии клавиш и кнопок мыши, создании и закрытии окон, изменении размеров и положения окна, выборе объекта в каком-либо списке, выборе команды в меню и т.п. Часто одно и то же действие пользователя (например, щелчок левой кнопкой мыши на пункте меню) вызывает посылку нескольких разных сообщений.

Большая часть посылаемых сообщений может обрабатываться самой системой по умолчанию. Прикладная программа может взять обработку некоторых сообщений на себя, чтобы обеспечить требуемую логикой работы реакцию на определенные события (например, при изменении размеров окна перераспределить его площадь между отдельными панелями; при попытке закрыть окно запросить подтверждение и т.п.). Программа должна также обрабатывать сообщения о посылаемых ей командах (при использовании меню, кнопок и т.п.) и сообщение о необходимости перерисовать содержимое окна.

При создании любого нового окна Windows требует задать оконную функцию, которая должна обрабатывать сообщения, направленные этому окну. Нить, получившая сообщение, либо вызывает оконную функцию одного из своих окон (для выбора окна используется функция DispatchMessage), либо обрабатывает сообщение сама (если оно не связано с конкретным окном).

Имеется два принципиально разных способа посылки сообщения. Посылая сообщение синхронно, отправитель дожидается окончания его обработки, прежде чем продолжить работу. Асинхронная посылка напоминает опускание письма в почтовый ящик, она не оказывает влияния на дальнейшую работу отправителя. Прикладная программа может посылать любые сообщения синхронным или асинхронным способом, как сочтет нужным разработчик. Для этого могут использоваться, соответственно, функция SendMessage (синхронная посылка) или функция PostMessage (асинхронная посылка).

Нить процесса получает сообщения, адресованные принадлежащим ей окнам или самой нити. Для выборки сообщений из очереди используется функция GetMessage. В случае отсутствия сообщений нить блокируется до их поступления. При желании программист может использовать функцию PeekMessage, которая проверяет наличие сообщения, не блокируя вызывающую нить.

Реализация асинхронного способа посылки достаточно проста: система просто помещает сообщение в очередь нити-получателя. Синхронная посылка в ранних, невытесняющих версиях Windows также реализовалась очень просто: как непосредственный вызов оконной функции окна-получателя, минуя всякие очереди. После окончания обработки управление возвращалось отправителю. В современных версиях Windows все так и происходит, если нить посылает сообщение своему собственному окну. В остальных случаях такое решение невозможно. Во-первых, если сообщение пересылается между нитями разных процессов, то изоляция процессов в памяти не дает возможности одной нити вызвать другую как подпрограмму. Во-вторых, даже для нитей одного процесса такой вызов мог бы привести к непредсказуемым последствиям в случае переключения нитей по кванту времени.

В результате этого системе приходится действовать очень аккуратно. Нить, пославшая синхронное сообщение окну другой нити, блокируется. Сообщение помещается в отдельную очередь синхронных сообщений к нити-получателю. Эта нить продолжает свою нормальную работу до момента, когда она обратится за очередным сообщением, т.е. вызовет GetMessage или PeekMessage. Тогда система вызывает оконные функции нити для обработки каждого из ожидающих синхронных сообщений. Только когда все они обработаны, выполняется вызванная функция GetMessage или PeekMessage, проверяющая наличие асинхронных сообщений в очереди.

В процессе обработки синхронного сообщения оконная функция не должна вызывать функции, которые могут вызвать переключение системы на выполнение другой нити.



Поделиться:


Последнее изменение этой страницы: 2017-02-05; просмотров: 909; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.15.235.196 (0.018 с.)