Методы синхронизации процессов
Решение с помощью семафоров задачи "читатели – писатели"
Суть задачи читатели-писатели в следующем: имеется общий ресурс и две группы процессов: читатели (которые могут только читать ресурс, но не изменяют его) и писатели (которые изменяют ресурс). В каждый момент работать с ресурсом может сразу несколько читателей (когда ресурс не изменяется писателями), но не более одного писателя. Необходимо синхронизировать их действия над ресурсом и обеспечить взаимное исключение соответствующих критических секций.
Будем использовать два семафора и целую переменную:
semaphore mutex = 1; semaphore wrt = 1; int readcount = 0;
Семафор mutex используется читателями для взаимного исключения операций над переменной readcount (счетчиком читателей). Семафор wrt используется для взаимного исключения писателей.
Реализация процесса-писателя особых комментариев не требует:
wait (wrt); . . . изменение ресурса . . . signal (wrt);
Реализация процесса-читателя несколько более сложна:
wait (mutex); readcount++; if (readcount == 1) { wait (wrt); } signal (mutex); . . . чтение ресурса . . . wait (mutex); readcount--; if (readcount == 0) { signal (wrt); }
Процесс-читатель, во-первых, должен увеличить значение readcount, причем обеспечить взаимное исключение для действий над readcount с помощью семафора mutex.Далее, если процесс является первым читателем, он должен закрыть семафор wrt, чтобы исключить одновременное с чтением изменение ресурса писателями. По окончании чтения ресурса, читатель в аналогичном стиле вновь уменьшает readcount. Если при этом оно обнуляется (т.е. это последний на данный момент читатель), то читатель открывает семафор wrt, сигнализируя писателям, что они могут изменять ресурс.
Решение с помощью семафоров задачи "обедающие философы"
Суть задачи обедающие философы в следующем. Имеется круглый стол, за которым сидят пять философов (впрочем, их число принципиального значения не имеет – для другого числа философов решение будет аналогичным). Перед каждым из них лежит тарелка с едой, слева и справа от каждого – две китайские палочки. Философ может находиться в трех состояниях: проголодаться, думать и обедать. На голодный желудок философ думать не может. Но начать обедать он может, только если обе палочки слева и справа от него свободны. Требуется синхронизировать действия философов. В данном случае общим ресурсом являются палочки. Иллюстрацией условий задачи является рис. 12.1.
Данная задача является одной из излюбленных задач профессора Ч. Хоара, который использовал ее как пример в своих работах по параллельному программированию.
При решении задачи будем использовать массив семафоров chopstick, описывающий текущее состояние палочек: chopstick[i] закрыт, если палочка занята, открыт – если свободна:
semaphore chopstick [5] = { 1, 1, 1, 1, 1};
Алгоритм реализации действий философа i имеет вид:
do { wait (chopstick [i]); /* взять левую палочку */ wait (chopstick [(i + 1) % 5]); /* взять правую палочку */ . . . обедать . . . signal (chopstick [i]); /* освободить левую палочку */ signal (chopstick [(i+1) % 5]); /* освободить правую палочку */ . . . думать . . . while (1);
Решение данной задачи с помощью семафоров оказывается особенно простым и красивым.
Критические области
Критические области (critical regions) – более высокоуровневая и надежная конструкция для синхронизации, чем семафоры. Общий ресурс описывается в виде особого описания переменной:
v: shared T
Доступ к переменной v возможен только с помощью специальной конструкции:
region v when B do S
где v – общий ресурс; B – булевское условие, S – оператор (содержащий действия над v ).
Семантика данной конструкции следующая. Пока B ложно, процесс, ее исполняющий, должен ждать. Когда B становится истинным, процесс получает доступ к ресурсу v и выполняет над ним операции S. Пока исполняется оператор S, больше ни один процесс не имеет доступа к переменной v.
Решение с помощью критических областей задачи "ограниченный буфер"
struct buffer { int pool [n]; int count, in, out } buf: shared buffer;
Алгоритм процесса-производителя:
region buf when (count < n) { pool [in] = nextp; in = (in+1) % n; count++; }
Заметим, что проверка переполнения буфера выполняется при входе в конструкцию region, и потребитель ждет, пока в буфере освободится хотя бы один элемент.
Алгоритм процесса-потребителя:
region buf when (count > 0) { nextc = pool [out]; out = (out+1) % n; count--; }
Нельзя не отметить, насколько проще и надежнее оказывается решение задачи с использованием данной конструкции, по сравнению с использованием семафоров.
Схема реализации критических областей с помощью семафоров
Будем использовать для реализации конструкции region x when B do S следующие семафоры и целые переменные:
semaphore mutex, first-delay, second-delay; int first-count, second-count;
Семафор mutex используется для взаимного исключения доступа к критической секции S. Семафор first-delay используется для ожидания процессов, которые не могут войти в критическую секцию S, так как условие B ложно. Число таких процесов хранится в переменной first-count. Семафор second-delay используется для ожидания тех процессов, которые вычислили условие B один раз и ожидают, когда им будет позволено повторно вычислить B ( second-count – счетчик таких процессов). Реализация предоставляется студентам в качестве упражнения.