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

Блокировки

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

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

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

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

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

- Не должны возникать взаимные блокировки и голодания.

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

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

- Любой процесс должен оставаться в критическом разделе только в течение ограниченного времени.

Пример: два процесса как потоки, т.е. могут польз. глоб. переменной.

Алгоритм 1.

nTurn – очередь какого раздела наступила

Код процесса 0

while (nTurn != 0)

/* пережидание */;

// критический раздел

nTurn = 1;

// . . .

Код процесса 1

while (nTurn != 1)

/* пережидание */;

// критический раздел

nTurn = 0;

// . . .

Проблемы

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

- блокировка при сбое другого процесса.

Алгоритм 2

Глоб. перем. – массив из 2х флагов.

Глобальные переменные

bool abFlags[2] = { false, false };

Код процесса 0

while (abFlags[1]) // (1)

/* пережидание */ ;

abFlags[0] = true; // (2)

// критический раздел

abFlags[0] = false;

Код процесса 1

while (abFlags[0]) // (1)

/* пережидание */ ;

abFlags[1] = true; // (2)

// критический раздел

abFlags[1] = false;

П роблемы:

В такой ситуации оба войдут в критический раздел

Алгоритм 3

Сначала отмечаем, что хотим войти, потом ожидаем.

Код процесса 0

… abFlags[0] = true; // (1)

… while (abFlags[1]) // (2)

/* пережидание */ ;

// критический раздел (3)

abFlags[0] = false;…

Код процесса 1

abFlags[1] = true; // (1)

//

while (abFlags[0]) // (2)

/* пережидание */ ;

// критический раздел (3)

abFlags[1] = false;

Проблемы:

- могут быть обы навечно заблокированы (взаимная блокировка, true одновременно)

Алгоритм 4

Код процесса 0

abFlags[0] = true; // (1)

while (abFlags[1]) // (2)

{

abFlags[0] = false; // (3)

// задержка

abFlags[0] = true; // (4)

}

// критический раздел

abFlags[0] = false;

Код процесса 1

abFlags[1] = true; // (1)

while (abFlags[0]) // (2)

{

abFlags[1] = false; // (3)

// задержка

abFlags[1] = true; // (4)

}

// критический раздел

abFlags[1] = false;

Проблемы:

Уступают друг другу > Бесконечно уступают друг другу (ленивая блокировка)

Алгоритм Деккера (Dijkstra, 1965)

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

Код процесса 0

abFlags[0] = true; // (1)

while (abFlags[1]) // (2)

if (nTurn == 1) // (3)

{

abFlags[0] = false; // (4)

while (nTurn == 1) // (5)

/* пережидание */ ;

abFlags[0] = true; // (6)

}

// критический раздел

nTurn = 1; // (7)

abFlags[0] = false; // (8)

Код процесса 1

abFlags[1] = true; // (1)

while (abFlags[0]) // (2)

if (nTurn == 0) // (3)

{

abFlags[1] = false; // (4)

while (nTurn == 0) // (5)

/* пережидание */ ;

abFlags[1] = true; // (6)

}

// критический раздел

nTurn = 0; // (7)

abFlags[1] = false; // (8)

Комбинация 1 и 4.

(2) – не освободили ли;

(3) – наступила ли очередь;

(4) – освобождаем

(5) – пережидаем чужую очередь

(6) – опять хотим войти

Блокировка с двойной проверкой. Не работает в SMP системах (архитектура многопроцессорных компьютеров, в которой два или более одинаковых процессоров подключаются к общей памяти), т.к. выполняется не в том порядке. Для решения проблемы ставится «барьер» (барьер памяти) - операция сброса кэша.

Другая проблема: nTurn не меняется в цикле, и компилятор выносит вне цикла. Решение – сделать переменную volatile. volatile - это квалификатор переменной, говорящий компилятору, что значение переменной может быть изменено в любой момент и что часть кода, которая производит над этой переменной какие-то действия (чтение или запись), не должна быть оптимизирована.

Алгоритм Петерсона (Peterson, 1981)

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

Код процесса 0

abFlags[0] = true; // (1)

nTurn = 1; // (2)

while (abFlags[1] && // (3)

nTurn == 1) // (4)

/* пережидание */ ;

// критический раздел

abFlags[0] = false; // (5)

Код процесса 1

abFlags[1] = true; // (1)

nTurn = 0; // (2)

while (abFlags[0] && // (3)

nTurn == 0) // (4)

/* пережидание */ ;

// критический раздел

abFlags[1] = false; // (5)

Когда нарушается одно из условий, входим в критический раздел. (Также volatile).

В С++ 2011 появились атомарные операции, которые запрещают менять местами (тогда уже нельзя назвать чисто программным алгоритмом)

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