Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Martin Harvey - Threads.doc
Скачиваний:
7
Добавлен:
01.03.2025
Размер:
1.36 Mб
Скачать

Атомарность ниоткуда

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

procedure DoSomethingCritical(var Lock: integer);

var

Temp: integer;

Begin

{ Initialize lock }

Lock := -1;

{ Enter Lock }

Repeat

Inc(Lock);

Temp := Lock;

if Temp > 0 then

Dec(Lock);

until not (Temp > 0);

{ Perform operations }

{ Leave Lock }

Dec(Lock);

end;

procedure AsmDoSomethingCritical(var Lock: integer);

asm

{ Initialize lock }

lock mov dword ptr[eax],$FFFFFFFF

{ Enter Lock }

@spin:

lock inc dword ptr[eax]

mov edx,[eax]

test edx,edx

jng @skipdec

lock dec dword ptr[eax]

@skipdec:

test edx,edx

jg @spin

{ Perform operations }

{ Leave Lock }

lock dec dword ptr[eax]

end;

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

Самое важное в этом наборе операций то, что при определенных условиях переключение потоков может произойти в любой момент времени, но потокобезопасность все же сохранится. Первое приращение замка является косвенным приращением регистра. Значение всегда находится в памяти, и приращение атомарно. Затем мы читаем значение замка в локальную переменную. Это действие не атомарное. Значение, прочитанное в локальную переменную, может отличаться от результата приращения. Тем не менее, хитрость состоит в том, что поскольку приращение выполняется перед действием чтения, происходящие конфликты потоков всегда будут приводить к слишком высокому прочитанному значению, а не к слишком низкому: в результате конфликтов потоков можно узнать, свободен ли замок.

Часто полезно писать подобные действия на ассемблере, чтобы быть полностью уверенным, что правильные значения останутся в памяти, а не кешируются в регистрах. Компилятор Delphi (по крайней мере Delphi 4), при передаче замка как var-параметра, и с использованием локальной переменной, генерирует корректный код, который будет работать на однопроцессорных машинах. На многопроцессорных машинах косвенные приращения и декременты регистра не атомарны. Эта проблема решена в ассемблерной версии кода путем добавления префикса lock перед инструкциями, которые имеют дело с замком. Этот префикс указывает процессору исключительно заблокировать шину памяти на все время выполнения инструкции, таким образом этими операции становятся атомарными.

Плохо только, что хотя это и теоретически правильно, виртуальная машина Win32 не позволяет процессам пользовательского уровня исполнять инструкции с префиксом lock. Программисты, предполагающие действительно применять этот механизм, должны использовать его только в коде с привилегиями нулевого кольца (ring 0). Другая проблема состоит в том, что поскольку эта версия блокировки не вызывает Sleep, потоки способны монополизировать процессор, пока они ожидают снятия блокировки, а это гарантирует зависание машины.

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