Я слышу крик в темноте Наверное, это сигнал. В. Бутусов |
Посмотрев на примеры 7.2 и 7.4, внимательный читатель
должен отметить, что используемая конструкция подозрительно похожа на
работу с внешними устройствами в режиме опроса. Действительно, опрос флаговой
переменном в цикле хотя и обеспечивает гарантию взаимоисключения, но обладает
всеми недостатками, которые мы указывали для опроса внешнего устройства.
g случае исполнения параллельных нитей на одном процессоре, данный метод
имеет еще один недостаток: пока одна из нитей занимается опросом, никакая
другая нить не может исполняться, потому что процессор загружен непродуктивной
работой.
Пример 7.5. Ошибка потерянного пробуждения (lost wake-up bug)
program пауза
var flag: Boolean;
procedure процесс1
var myflag: Boolean
while True do
begin
myflag := True;
testandset(myflag, flag);
if myflag then
(* Обратите внимание, что проверка флага *
* и засыпание — это разные операторы! *)
pause;
критическаясекция();
flag := False;
end
end;
Одно из решений состоит в усложнении примитива pause:
он должен засыпать, если и только если сигнал еще не приходил. Усложнение
получается значительное: мало того, что перед засыпанием надо проверять
нетривиальное условие, необходимо еще предусмотреть какой-то способ сброса
этого условия, если мы предполагаем многократное использование нашего
примитива.
Если писать на ассемблере или родственных ему языках, можно пойти и более
изощренным путем (пример 7.6). Подмена адреса возврата в обработчике прерывания
гарантирует нам, что если прерывание по установке флага произойдет в промежутке
между метками label и ok,
мы перейдем на метку label и, вместо того, чтобы
заснуть навеки, благополучно проверим флаг и войдем в критическую секцию.
Пример 7.6. Обход ошибки потерянного пробуждения
.globl flag
flag: db 0
jmpbuf: dw 0
proc flag_interrupt
push eax
tst jmpbuf
bz setflagonly
; подменяем адрес возврата
move eax, jmpbuf
move sp[RETURN_ADDRESS_OFFSET] , eax setflagonly
move eax, 1
move flag, eax
pop eax
iret endp
proc process!
inove eax, setjmp
move jmpbuf, eax setjmp:
move eax, 1
lock xchg eax, flag
tst eax
bz ok
halt
ok
xor eax, eax move jmpbuf, eax
критическая секция
xor eax, eax move flag, eax
endp
Более элегантный и приемлемый для языков высокого уровня
путь решения этой проблемы состоит в том, чтобы объединить в атомарную
операцию проверку флага и засыпание. Нас с читателем можно поздравить
с изобретением двоичного семафора Дейкстры.