Нижегородский государственный университет им. Н.И.Лобачевского
Опубликован: 02.10.2012 | Доступ: свободный | Студентов: 1744 / 191 | Длительность: 17:47:00
Специальности: Программист
Лекция 3:

Операционные системы - аспекты параллелизма

< Лекция 2 || Лекция 3: 123456 || Лекция 4 >

3.5.2. Мониторы

Монитор – это конструкция языка программирования, поддерживающая управляемый доступ к разделяемым данным. Монитор инкапсулирует:

  • разделяемые критические данные;
  • функции, использующие разделяемые данные;
  • синхронизацию выполнения параллельных потоков, вызывающих указанные функции.

Доступ к данным, расположенным в мониторе, реализуется только посредством вызова предоставленных функций. Только один поток может находиться в мониторе в любой момент времени, если второй поток пытается вызвать метод монитора, он переходит в состояние ожидания до выхода из монитора первого потока. Код синхронизации добавляется компилятором (см. рис. 3.6).

Монитор

Рис. 3.6. Монитор

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

Однако, возможны ситуации, в которых использование монитора приводит к некорректным результатам. Например, предположим, что разделяемыми данными является некоторый буфер, над которым определены операции добавления элемента и изъятия элемента. Изначально буфер пуст. Что произойдет, если первым будет выполнен запрос на изъятия элемента?

Тупиковая ситуация при использовании монитора

Рис. 3.7. Тупиковая ситуация при использовании монитора

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

  • wait(cv) – выполняется потоком, который хочет подождать наступления события. Снимает блокировку монитора, после чего другой поток может войти в него; ожидает, пока какой-либо другой поток не подаст условный сигнал.
  • signal(cv) – выполняется потоком, сигнализирующем о наступлении события. Пробуждает максимум один ожидающий поток; если отсутствуют потоки, ожидающие события, информация о приходе сигнала теряется.
  • broadcast(cv) – также выполняется потоком, сигнализирующем о наступлении события. Пробуждает все ожидающие события потоки.

В реализациях для условных переменных часто организуются очереди ожидающих их потоков. Кроме того, условные переменные часто реализуются как самостоятельный механизм синхронизации, а не составляющий элемент мониторов (Event в Win32, conditional variable библиотеки POSIX и т.д.)

Существует два типа мониторов, различным образом обрабатывающих поступление сигнала о произошедшем событии: мониторы Хоара и мониторы Меса.

Мониторы Хоара обрабатывают вызов signal(cv) следующим образом:

  • немедленно запускается поток, ожидавший сигнала;
  • поток, пославший сигнал, блокируется и остается блокированным все время, пока выполняется поток, которого он вывел из состояния ожидания.

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

Мониторы Меса обрабатывают вызов signal(cv) несколько другим способом:

  • ожидающий поток переводится в состояние "готов к выполнению", а поток, пославший сигнал, продолжает исполнение;
  • ожидавший поток запускается при выходе потока, пославшего сигнал, из монитора или его перехода в состояние ожидания.

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

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

Монитор Хоара:

if( not Ready )
wait(c);
Монитор Меса:
while( not Ready )
wait(c);

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

3.5.3. Синхронные сообщения

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

Обычно реализуются два типа доставки сообщений: синхронная и асинхронная.

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

Сообщения позволяют организовать синхронизацию выполнения потоков.

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

/* Код i-ого обработчика */
while( обработка_не_завершена ){
<обработка части данных i-ого потока >
SendMessage(1-i); /*Отправить сообщение потоку 1-i*/
ReceiveMessage(1-i);/*Получить сообщение от потока 1-i*/
}
В данном решении можно использовать как синхронные, так и асинхронные
сообщения, а вот в следующем – только синхронные.
// Код потока 0
while(обработка_не_завершена){
<обработка части данных 0>
SendMessage(1);
}
// Код потока 1
while(обработка_не_завершена){
<обработка части данных 1>
ReceiveMessage(0);
}

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

3.6. Типовые задачи синхронизации

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

- "Производители-Потребители"
- "Читатели-Писатели"
- "Обедающие философы"
- "Спящий брадобрей"

3.6.1. Задача "Производители-потребители"

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

Два потока, обменивающихся информацией через циклический буфер

Рис. 3.8. Два потока, обменивающихся информацией через циклический буфер

Функциональность потоков производителя и потребителя можно записать следующим образом.

Producer:
while(true) {
PrepareData(); // Подготовить данные
Put(Data); // Поместить данные в циклический буфер
}
Consumer:
while(true) {
Get(&Data); // Считать данные из циклического буфера
UseData(); // Использовать данные
}

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

Producer:
while(true) {
PrepareData(); // Подготовить данные
while( ! Put(Data) ) // Разместить данные
57
;
}
Consumer:
while(true) {
while( ! Get(&Data) ) // Считать данные
;
UseData(); // Использовать данные
}

Задача "Производители-Потребители" заключается в обеспечении согласованного доступа нескольких потоков к разделяемому циклическому буферу. Корректное решение должно удовлетворять следующим условиям:

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

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

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

Решение задачи "Производитель-Потребитель", использующее семафоры

Semaphore Access = 1;//управляет доступом к разделяемым данным
Semaphore Empty = n; // количество пустых записей
Semaphore Full = 0; // количество заполненных записей
Producer(){
P(Empty); // ждем появления свободной записи
P(Access); // получаем доступ к указателям
<записываем значение в запись>
V(Access); // завершили работу с указателями
V(Full); // сигнализируем о появлении заполненной записи
}
Consumer(){
P(Full); // ждем появления заполненной записи
P(Access); // получаем доступ к указателям
<извлекаем данные из записи>
V(Access); // завершили работу с указателями
V(Empty); // сигнализируем о появлении свободной записи
<используем данные из записи>
}

Двоичный семафор Access используется для организации согласованного доступа к критическим данным, счетные семафоры Full и Empty используются для сигнализации ожидающим потокам о наступлении ожидаемого события (появлении заполненной или пустой записи соответственно).

Решение задачи "Производитель-Потребитель", использующие мониторы

Monitor Bounded_buffer {
buffer Resources[N];
condition not_full, not_empty;
Produce(resource x) {
while( array "resources" is full )
wait(not_full);
^записываем значение "x" в запись массива "Resources">
signal(not_empty);
}
Consume(resource *x) {
while( array "resources" is empty )
wait(not_empty);
59
*x = <считываем значение из массива "Resources">
signal(not_full);
}
}

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

< Лекция 2 || Лекция 3: 123456 || Лекция 4 >
Дмитрий Остапенко
Дмитрий Остапенко

поддерживаю выше заданые вопросы

 

Павел Каширин
Павел Каширин

Скачал архив и незнаю как ничать изучать материал. Видео не воспроизводится (скачено очень много кодеков, различных плееров -- никакого эффекта. Максимум видно часть изображения без звука). При старте ReplayMeeting и Start в браузерах google chrome, ie возникает script error с невнятным описанием. В firefox ситуация еще интереснее. Выводится: 

Meet Now: Кукаева Светлана Александровна. 

Meeting Start Time: 09.10.2012, 16:58:04
Meeting Stop Time: 09.10.2012, 18:45:18
Recording Duration:01:47:14

Downloading...

Your Web browser is not configured to play Windows Media audio/video files.

Make sure the features are enabled and available.