Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
экзамен СП.docx
Скачиваний:
13
Добавлен:
22.04.2019
Размер:
515.86 Кб
Скачать

Синхронизация субъектов взаимодействия

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

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

Напомним, что любой субъект (процесс, поток или иная единица планирования процессорного времени) может находиться в одном из нескольких состояний: A (активность), R (готовность), W (ожидание) или P (пассивность, неготовность) (см. выше). При этом переключения между состояниями A и R контролируются только планировщиком и в данной теме не рассматриваются, а между A (или R) и W – зависят от среды выполнения процессаи выполняемых им действий.

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

Как правило, синхронизация требуется при совместном использовании того или иного объекта (ресурса).

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

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

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

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

Программы, обладающие критическими секциями, должны соответствовать определенным требованиям:

– внутри критической секции может одновременно находится не более 1 субъекта (фактически это требование вытекает из определения критической секции и было изложено выше);

– приоритеты внутри критической секции не различаются (т.е. субъекты не прерывают друг друга);

– останов любого из субъектов вне критической секции безразличен для прочих (независимость субъектов);

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

– срок пребывания любого субъекта внутри критической секции конечен (предотвращение блокировки секции одним из субъектов);

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

Значимость требований может быть проиллюстрирована примерами более сложных задач из теории параллельного программирования.

1. Задача "производитель-потребитель". Во взаимодействии участвуют 2 субъекта, один из которых "производит" (формирует) некоторый ресурс, а второй – использует его. Например, производитель заполняет буфер данных в памяти, потребитель считываете его. Необходимо обеспечивать лишь контроль правильной последовательности обращений и исключение совместного доступа к элементам данных.

2. Задача "писатели-читатели". Отличается тем, что и "писателей", и "читателей" может быть несколько, поэтому исключение совместного доступа усложняется: захват ресурса "писателем" блокирует его для любого другого использования, захват "читателем" – блокирует только обращения "писателей", другие "читатели" могут использовать ресурс совместно. Данная ситуация типична для различного рода банков и баз данных, причем интенсивность обращений "читателей" обычно преобладает.

3. Задача об обедающих философах. Сформулирована Дейкстрой как весьма удобная и полная модель асинхронного взаимодействия. В кратком виде она сводится к следующему. 5 философов живут в пансионате в отдельных комнатах и собираются только за общим обеденным столом, который оборудован общим блюдом со спагетти (считается неисчерпаемым), 5 стульями, 5 тарелками и 5 вилками. Для еды философу нужны тарелка и 2 вилки, блюдом может пользоваться любое число философов одновременно. За столом философ может либо есть, либо сидеть и размышлять, причем общение между ними отсутствует. В этой модели философы соответствуют процессам (субъекты взаимодействия), блюдо – некритический разделяемый ресурс, вилки – критический ресурс.

Модель позволяет представить ряд ситуаций, из которых наиболее типичны:

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

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

В тех же терминах могут быть намечены и некоторые пути разрешения конфликтов:

– случайный выбор философом вилки, которую тот берет первой (левая или правая), вероятность тупика при этом снижается, но не может быть устранена полностью;

– философ обязательно дожидается освобождения обоих ближайших вилок и лишь затем захватывает их;

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

В плане технической реализации этих и других решений существует множество методов и механизмов, некоторые из которых рассмотрены ниже.

1. Решение Деккера – исторически наиболее раннее (начало 60-х гг.), состоит в наличии механизма обслуживания общих переменных, причем реализованного в каждой программе. Это число программное решение, не требующее поддержки со стороны ОС и не рассчитанное на системы с реальной многозадачностью, причем достаточно сложное; в настоящее время большого практического интереса не представляет.

2. Семафор Дейкстры – представляет собой целую неотрицательную переменную S, по отношению к которой определены примитивы инкремента V(S) и условного декремента P(S). Примитивы выполняются атомарно, т.е. не прерываются планировщиком и другими программами, за исключением попытки декремента при нулевом значении S, когда процесс переводится в состояние ожидания, пока семафор не станет ненулевым, после чего декремент и возврат управления процессу произойдут опять-таки атомарно. Семафор позволяет эффективно защищать критическую секцию и более гибок, чем простой двоичный флаг. Кроме того, существуют многочисленные модификации семафоров: векторные, многозначные, отрицательные, с неединичным шагом и т.д.

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

4. События и событийное управление. Событиями принято называть факт выполнения некоторых условий, наступивший в системе, информация о наступлении события может распространяться различными способами начиная от прерываний. Суть событийного управления состоит в том, что программы представляются совокупностью функциональных модулей (подпрограмм), которые активизируются только для выполнения определенных действий и напрямую друг с другом не взаимодействующих. Модули активизируются системой в соответствии с возникающими событиями, после завершения выполнения модуля управление вновь передается системе, а прикладная программа переходит в состояние ожидания (сна). Т.о., исключается "нагруженный" простой программ, а управление ими становится унифицированным и подконтрольным системе, что позволяет строить крупные сложные программные комплексы. Основными недостатками являются недостаточная гибкость межмодульного интерфейса и, возможно, повышенная ресурсоемкость, в результате чего на практике событийная передача управления обычно комбинируется с обычными вызовами подпрограмм. Интерес представляют такие способы информирования о событиях, как прерывания, сигналы и сообщения. Прерывания рассматривались в курсе системного программирования DOS, приближены к аппаратным средствам и мало актуальны в многозадачных ОС (хотя и могут быть доступны, в модифицированном виде на платформе x86 известны как исключения). Сигналы представляют собой специальные системные объекты, характеризуемые в первую очередь типом и получателем. Должны быть определены как минимум две функции работы с сигналами: send() – генерация (посылка) сигнала определенному процессу или группе) и wait() – ожидание получения сигнала, в т.ч. определенного типа; последняя, естественно, переводит процесс в состояние ожидания до появления нужного сигнала. Сообщения отличаются от них большей информативностью (имеют дополнительные параметры) и более сложным механизмом передачи и распространения, в т.ч. с использованием очереди сообщений. В использовании сообщения подобны сигналам, включая и состояние ожидания при приеме. Оба механизма весьма универсальны и в том или ином виде реализуются в большинстве ОС, а также служат основой для более сложных технологий. Типичный пример применения сигналов – сигналы в ОС UNIX (классические и "надежные" POSIX). Механизм сообщений является базовым для ОС Windows.

5. Рандеву – алгоритм взаимодействия, позволяющий решить задачи как синхронизации, так и обмена. Основная идея состоит в том, что процессы первоначально обмениваются извещениями о намерении взаимодействовать, затем, после взаимного подтверждения, организуется прямой обмен без буферизации. Симметричное рандеву предложено Хоаром в 1978 г. и предусматривает знание обоими процессами имен своих партнеров; в символической записи используются т.н. операторы Хоара: ! – передача данных посредством рандеву, ? – прием. Для ослабления проблемы именования Хансеном была предложена асимметричное рандеву, при котором ведущий процесс ("хозяин", master) обращается по имени к одному из известных служебных процессов ("слуга", slave), который, в свою очередь, отвечает "хозяину" без указания имени. Т.о., "слуги" соответствуют доступным сервисным функциям (системным либо прикладным), а обратную идентификацию обратившегося к "слуге" "хозяина" обеспечивает система. Такая схема реализована в качестве базовой в языке ADA и имеет аппаратную поддержку в процессоре iAPX432 (программируется непосредственно на ADA). Механизм рандеву отличается высокой гибкостью, мощностью и универсальностью, но сильно загружает систему интенсивным потоком запросов и подтверждений, а также большим количеством служебных процессов, порождаемых при взаимодействии основных.

Примечание. Очевидно, в роли имен субъектов на практике могут выступать их адреса, идентификаторы, описатели и т.п.

6. Архитектура клиент-сервер может рассматриваться как развитие асимметричного рандеву, но традиционно отделяется от нее. В системе должны быть определены процесс-сервер (загружен постоянно, принимает и обрабатывает запросы клиентов) и процессы-клиенты (выполняют прикладные задачи, обращаясь при этом к серверу). Синхронизация требуется только между конкретным клиентом и сервером, организация совместного выполнения более чем одного запроса считается внутренним делом сервера, поэтому клиенты не зависят друг от друга. В зависимости от контекста термин "клиент-сервер" может иметь ряд значений. В плане обеспечения взаимодействия можно выделить как особый случай обмен с установлением соединения (виртуального канала), при этом после прохождения процедуры установления соединения данные могут передаваться через структуру, более или менее подобную открытому файлу, причем с контролем доставки отдельных порций. Подобная схема может быть несколько громоздка, но достаточно эффективна и универсальна, ее поддержка предусмотрена в большинстве ОС, особенно широкое распространение она получила как технология связи в вычислительных сетях. В частности, протокол TCP (один из базовых в Internet) и программный интерфейс сокетов (присутствует практически во всех ОС) ориентированы на такой тип взаимодействия.

Далее рассмотрены некоторые механизмы исключения и синхронизации, предусмотренные в Win 32.

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

Блокированные обращения

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

InterlockedDecrement

InterlockedIncrement

InterlockedExchange

InterlockedExchangeAdd

InterlockedCompareExchange

Объект "критическая секция"

Классический механизм критической секции поддерживается следующим образом. Необходимо объявить переменную типа CRITICAL_SECTION (определена как ULONG и фактически является идентификатором соответствующего объекта) и инициализировать ее вызовом функции InitializeCriticalSection. После этого любой поток программы может использовать функции EnterCriticalSection и LeaveCriticalSection для ограждения критического раздела кода. В случае "занятости" критической секции другим потоком EnterCriticalSection переводит вызвавший ее поток в состояние ожидания, которое может быть прервано только освобождением критической секции. В API Windows NT имеется функция TryEnterCriticalSection типа BOOL, которая в случае "занятости" критической секции немедленно возвращает "ложное" значение. Функция DeleteCriticalSection удаляет объект "критическая секция" и освобождает выделенные для него ресурсы.

Применимость данной реализации механизма критических секций ограничена рамками одного процесса.

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

Функции ожидания

Для более сложных методов синхронизации, в том числе между потоками различных процессов, используются т.н. функции ожидания: WaitForSingleObject, WaitForSingleObjectEx, SignalObjectAndWait, WaitForMultipleObjects, WaitForMultipleObjectsEx, MsgWaitForMultipleObjects, MsgWaitForMultipleObjectsEx. Их действие основано на использовании глобальных (контролируемых ядром системы) объектах, состояния которых могут интерпретироваться как “signaled” (приблизительно можно переводить как "установлено", "открыто", фактически означает "свободность", т.е. "не-занятость" объекта) и “non-signaled” (означает "занятость"). Вызов функции ожидания применительно к объекту в состоянии “signaled” дает немедленное успешное завершение, причем состояние объекта может быть изменено в зависимости от его типа, а в состоянии “non-signaled” – перевод вызвавшего процесса в состояние ожидания (блокировки) до "освобождения" объекта. Практически любые объекты ядра (процессы, файлы, транспортеры и т.д.) могут выступать в роли синхронизирующих, однако существует набор объектов, специально предназначенных для синхронизации потоков – т.н. Interprocess Synchronization Objects (ISO). Отметим, что файловые объекты, выступая в качестве синхронизирующих, выполняют важную функцию – обеспечиваю работу асинхронного ввода-вывода (см. соответствующий раздел).

Все объекты ISO идентифицируются их именами, причем из единого пространства имен, не пересекающегося с именами объектов файловой системы. Для доступа к ним служат описатели (тип HANDLE). Создание объекта выполняется вызовом Create... (можно выполнять его несколько раз, получая доступ к одному и тому же объекту), доступ к существующему – Open..., описатель объекта можно дублировать и наследовать, он удаляется явно функцией CloseHandle, либо автоматически – при завершении процесса-владельца. Объект существует, пока действителен хотя бы один его описатель.

Объекты Event ("Событие")

События – самая примитивная разновидность синхронизирующих объектов. Они порождаются функцией CreateEvetnt и бывают двух типов с “ручным” (manual) и с “автоматическим” сбросом. Объект "событие" может находиться в двух состояниях: “занят” (non-signaled) и “свободен” (signaled). Для перевода объекта событие в свободное состояние используется функция SetEvent, а для перевода в занятое – ResetEvent. С помощью любой из функций ожидания можно перевести вызвавший поток в состояние блокировки до освобождения объекта событие. Если объект событие является объектом “с автоматическим сбросом”, то функция ожидания автоматически переведет его в состояние занятого.

Объекты "событие" активно используются при асинхронном файловом вводе/выводе.

Объекты Mutex

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

Объекты Semaphore ("Семафор")

С помощью объектов семафор реализуется классический механизм семафоров. При этом в качестве примитива P(S) выступает одна из функций ожидания (уменьшает счетчик на единицу, но не меньше нуля, или ждет такой возможности), а в качестве примитива V(S) – функция ReleaseSemaphore (увеличивает счетчик семафора на заданную величину). При создании семафора функцией CreateSemaphore можно определить максимально допустимое значение переменной S.

Объекты Waitable Timer

Определены только в Windows NT.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]