Решение задачи «поставщик – потребитель» является характерным примером использования семафорных операций. Содержательная постановка этой задачи уже была нами описана в первом разделе данной главы. Разделяемыми переменными здесь являются счетчики свободных и занятых буферов, которые должны быть защищены со стороны обоих процессов, то есть действия по посылке и получению сообщений должны быть синхронизированы.
Использование семафоров для решения данной задачи приведено в листинге 6.11.
Листинг 6.11. Решение задачи «поставщик – потребитель»
Здесь переменные S_свободно, S_заполнено являются числовыми семафорами, S_взаимоискл – двоичный семафор. S_ свободно имеет начальное значение, равное N, где N – количество буферов, с помощью которых процессы связываются. Предполагается, что в начальный момент количество свободных буферов равно N; соответственно, количество занятых равно нулю. Двоичный семафор S_взаимноискл гарантирует, что в каждый момент только один процесс сможет работать с критическим ресурсом, выполняя свой критический интервал. Семафоры S_свободно и S_заполнено используются как счетчики свободных и заполненных буферов.
Действительно, перед посылкой сообщения «поставщик» уменьшает значение S_свободно на единицу в результате выполнения P(S_свободно), а после посылки сообщения увеличивает значение S_заполнено на единицу в результате выполнения \/(S_заполнено). Аналогично, перед получением сообщения «потребитель» уменьшает значение S_заполнено в результате выполнения Р(S_заполнено), а после получения сообщения увеличивает значение S_свободно в результате выполнения \/(S_свободно). Семафоры S_заполнено, S_свободно могут также использоваться для блокировки соответствующих процессов. Если пул буферов оказался пустым и к нему первым обратится процесс «потребитель», он заблокируется на семафоре S_заполнено в результате выполнения Р(S_заполнено). Если пул буферов заполнен и к нему в этот момент обратится процесс «поставщик», то он будет заблокирован на семафоре S_свободно в результате выполнения Р(S_свободно).
В решении задачи о «поставщике» и «потребителе» общие семафоры применены для учета свободных и заполненных буферов. Их можно также применить и для распределения иных ресурсов.
Можно использовать семафорные операции для решения таких задач, в которых успешное завершение одного процесса связано с ожиданием завершения другого. Предположим что существуют два процесса ПР1 и ПР2. Необходимо, чтобы процесс ПР1 запускал процесс ПР2 с ожиданием его выполнения, то есть ПР1 не продолжал бы выполняться до тех пор, пока процесс ПР2 до конца не выполнит свою работу. Программа, реализующая такое взаимодействие, представлена в листинге 6.12.
Листинг 6.12. Пример синхронизации процессов
Var
S : Semaphore:
Begin
InitSem(S, 0);
ПР1:
Begin
ПР11; {первая часть ПР1}
ON (ПР2); {поставить на выполнение ПР2}
P(S);
ПР12: {оставшаяся часть ПР1}
STOP
End;
ПР2:
Begin
ПР2; {вся работа программы ПР2}
V(S);
STOP;
End;
End.
Начальное значение семафора S равно нулю. Если процесс ПР1 начал выполняться первым, то через некоторое время он поставит на выполнение процесс ПР2, после чего выполнит операцию P( S) и «заснет» на семафоре, перейдя в состояние пассивного ожидания. Процесс ПР2, осуществив все необходимые действия, выполнит примитив V(S) и откроет семафор, после чего процесс ПР1 будет готов к дальнейшему выполнению.