
5.6 Примитивы взаимоисключения
Параллельная программа, представленная на рис. 5.2, правильно реализует механизм подсчета строк. Для простоты предположим, что действуют только два параллельных процесса. Управлять п параллельными процессами значительно сложнее.
Представленные в программе рис. 5.2 конструкции входвзаимоисключения и выходвзаимоисключения в каждом процессе содержат участок программы, который обеспечивает доступ к разделяемой переменной СТРОКВВЕДЕННЫХ, т. е. эти конструкции обрамляют критические участки.
Иногда подобные операторы называют примитивами взаимоисключения, другими словами, они вызывают выполнение самых фундаментальных операций, обеспечивающих реализацию взаимоисключения.
В случае двух процессов эти примитивы работают следующим образом. Когда «процессодин» выполняет примитив «входвзаимоисключения» (и если «процессдва» в это время находится вне своего критического участка) он входит в свой критический участок, обращается к разделяемой переменной, а затем выполняет «выходвзаимоисключения», показывая тем самым, что он вышел из критического участка.
Если «процессодин» выполняет «входвзаимоисключения», в то время как «процессдва» находится в своем критическом участке, «процессодин» переводится в состояние ожидания, пока «процессдва» не выполнит «выходвзаимоисключения». Только после этого «процессодин» сможет войти в свой критический участок.
program взаимоисключение;
var строквведенных: целое;
procedure процессодин;
while истина do begin
взятиеследующейстрокистерминала;
входвзаимоисключения;
строквведенных: = строквведенных + 1;
выходвзаимоисключения;
обработкастроки
end;
procedure процессдва;
while истина do begin
взятиеследующейстрокистерминала;
входвзаимоисключения;
строквведенных: = строквведенных + 1;
выходвзаимоисключения;
обработкастроки
end;
begin
строквведенных : = 0;
parbegin
процессодин;
процессдва parend
end;
Рис. 5.2 Применение примитивов взаимоисключения.
Если «процессодин» и «процессдва» выполняют «входвзаимоисключения» одновременно, то одному из них будет разрешено продолжить работу, а другому придется ждать, причем мы будем предполагать, что «победитель» выбирается случайным образом.
5.7 Реализация примитивов взаимоисключения
Мы хотим найти реализации примитивов «входвзаимоисключения» (код входа взаимоисключения) и «выходвзаимоисключения» (код выхода взаимоисключения) с соблюдением следующих четырех ограничений:
• Задача должна быть решена чисто программным способом на машине, не имеющей специальных команд взаимоисключения.
Каждая команда машинного языка выполняется как неделимая операция, т. е. каждая начатая операция завершается без прерывания.
Мы будем считать, что в случае одновременных попыток нескольких процессоров обратиться к одному и тому же элементу данных возможные конфликты разрешаются аппаратно при помощи схемы защитной блокировки памяти.
Эта схема защитной блокировки распараллеливает конфликтующие обращения к памяти со стороны отдельных процессоров, выстраивая их в очередь, т. е. разрешая производить только одно обращение в каждый конкретный момент времени. Предположим, что эти отдельные обращения обслуживаются в случайном порядке.
• Не должно быть никаких предположений об относительных скоростях выполнения асинхронных параллельных процессов.
• Процессы, находящиеся вне своих критических участков, не могут препятствовать другим процессам входить в их собственные критические участки.
• Не должно быть бесконечного откладывания момента входа процессов в их критические участки.
Изящную программную реализацию механизма взаимоисключения впервые предложил голландский математик Деккер. Рассмотрим усовершенствованный вариант алгоритма Деккера, разработанный Дейкстрой.