Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы ОС.docx
Скачиваний:
45
Добавлен:
22.09.2019
Размер:
3.18 Mб
Скачать

15. Взаимное исключение с активным ожиданием. Алгоритмы переменной-замка, строгого чередования, флагов готовности, Петерсона. (Лекция 4)

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

Активное ожидание (busy waiting) – исполнение цикла в ожидании наступления события (освобождения ресурса).

Блокировка процессов на основе активного ожидания – спин-блокировка. Применяется в ядре ОС.

Основное допущение: в момент выполнения оператора вытеснение потока не происходит (атомарность).

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

- запрещение прерывания – самое простое решение. Запрещение всех прерываний при входе процессоров в критическую область и разрешение прерываний по выходе из области. Это решение нецелесообразно. Если все прерывания отключились и возник какой-то сбой, ОС закончит свое существование. Если система многопроцессорная, второй процессор все равно может зайти в критическую область.

- переменные блокировки.

- строгое чередование.

- алгоритм Петерсона.

- команда TSL (Test and Set Lock – проверить и заблокировать). Это решение требует участия аппаратного обеспечения. Многие компьютеры имеют команду: TSL RX, LOCK. В регистр RX считывается содержимое слова памяти LOCK, в ячейке памяти LOCK сохраняется некоторое ненулевое значение. Операция считывания слова неделима. Процессор, выполняющий команду TSL, блокирует шину памяти, чтобы остальные процессоры, если они есть, не могли обратиться к памяти.

Переменная-замок (блокировки): Реализация взаимного исключения: нет при одновременном входе в секцию

Взаимное блокирование: нет

Препятствие входу: никогда

Возьмем некоторую переменную, доступную всем процессам, с начальным значением равным 0. Процесс может войти в критическую секцию только тогда, когда значение этой переменной-замка равно 0, одновременно изменяя ее значение на 1 – закрывая замок. При выходе из критической секции процесс сбрасывает ее значение в 0 – замок. Тут также может возникнуть состояние состязания, когда два процесса одновременно считывают переменную блокировки, когда она равна 0 и оба входят в критическую область.

shared int lock = 0;

/* shared означает, что переменная является разделяемой */

while (some condition) {

while(lock); lock = 1;

critical section

lock = 0;

remainder section}

Но такое решение не удовлетворяет условию взаимоисключения, так как действие while(lock); lock = 1; не является атомарным. Допустим, процесс P0 протестировал значение переменной lock и принял решение двигаться дальше. В этот момент, еще до присвоения переменной lock значения 1, планировщик передал управление процессу P1. Он тоже изучает содержимое переменной lock и тоже принимает решение войти в критический участок. Мы получаем два процесса, одновременно выполняющих свои критические секции.

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

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

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

shared int turn = 0;

while (some condition) {

while(turn != i);

critical section

turn = 1-i;

remainder section }

Взаимоисключение гарантируется, процессы входят в критическую секцию строго по очереди: P0, P1, P0, P1, P0, но алгоритм не удовлетворяет условию прогресса. Например, если значение turn равно 1, и процесс P0 готов войти в критический участок, он не может сделать этого, даже если процесс P1 нах-ся в remainder section.

Флаги готовности:

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

shared int ready[2] = {0, 0};

Когда i-й процесс готов войти в критическую секцию, он присваивает элементу массива ready[i] значение равное 1. После выхода из критической секции он, естественно, сбрасывает это значение в 0. Процесс не входит в критическую секцию, если другой процесс уже готов к входу в критическую секцию или находится в ней.

while (some condition) {

ready[i] = 1;

while(ready[1-i]);

critical section

ready[i] = 0;

remainder section

}

Полученный алгоритм обеспечивает взаимоисключение, позволяет процессу, готовому к входу в критический участок, войти в него сразу после завершения эпилога в другом процессе, но все равно нарушает условие прогресса. Пусть процессы практически одновременно подошли к выполнению пролога. После выполнения присваивания ready[0]=1 планировщик передал процессор от процесса 0 процессу 1, который также выполнил присваивание ready[1]=1. После этого оба процесса бесконечно долго ждут друг друга на входе в критическую секцию. Возникает ситуация, которую принято называть тупиковой (deadlock).

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

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

bool interested[2];

int turn;

void EnterRegion(int threadId)

{ int other = 1 - threadId; // Идентификатор второго потока

interested[threadId] = true; // Индикатор интереса текущего потока

turn = other; // Флаг очереди исполнения

/* Цикл ожидания, мы находимся в этом цикле, если второй процесс выполняет свою

критическую секцию. Как второй процесс выйдет из критической секции, выполнится

процедура LeaveRegion(int threadId), флаг заинтересованности (interested[other])

станет равен false, и цикл закончится. */

while( turn == other && interested[other] );}

void LeaveRegion(int threadId)

{interested[threadId] = false;}

Потоки вызывают EnterRegion последовательно: Поток с номером 0 вызывает EnterRegion, задавая этим индикатор своей «заинтересованности», устанавливая флаг очереди так, чтобы уступить очередь исполнения потоку номер 1. Поскольку последний пока еще не «заинтересован» в попадании в критическую область, выполнение сразу же возвращается из EnterRegion, и поток 0 входит в нее. Теперь EnterRegion вызывается потоком 1, для которого также выполняются описанные выше действия. Но так как поток 0 все еще «заинтересован» (interested[0] == true), выполнение остается в EnterRegion - поток 1 в ожидании (повторение инструкции для цикла 'while'). Как только поток 0 вызывает LeaveRegion и сбрасывает флаг своей «заинтересованности», поток 1 входит в критическую область и в конце сам вызывает LeaveRegion.

Потоки вызывают EnterRegion почти одновременно: Потоки почти одновременно вызывают EnterRegion, устанавливая тем самым флаг своей «заинтересованности» и уступая очередь выполнения конкурирующему потоку посредством установки значения переменной turn. Поскольку последним это делает поток 1, ему уже придется ждать в цикле, в то время как поток 0 беспрепятственно входит в критическую область кода. Ожидание потока 1 выражено повторением инструкции while для цикла ожидания. После того, как поток 0 выходит из критической области и сбрасывает флаг своей «заинтересованности», поток 1 продолжает свое исполнение и в конце сам сбрасывает соответствующий флаг вызовом LeaveRegion.