- •Глава 8. Параллельное выполнение процессов
- •8.1. Постановка проблемы
- •8.2. Взаимное исключение запретом прерываний
- •8.3. Взаимное исключение через общие переменные
- •Вариант 2: переменная-переключатель
- •Алгоритм Деккера
- •Алгоритм Питерсона
- •8.4. Команда testAndSet и блокировки
- •Xchg al,lock
- •8.5. Семафоры
- •8.6. "Производители–потребители"
- •8.7. Конструкции критических секций в языках программирования
- •8.8. Мониторы
- •8.9. "Читатели–писатели" и групповые мониторы
- •8.10. Примитивы синхронизации в языках программирования
- •8.11. Рандеву
- •Контрольные вопросы
Алгоритм Питерсона
Более компактная и изящная модификация алгоритма Деккера известна как алгоритм Питерсона. Вот его вариант для двух процессов:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static int right; static char wish[2] = { 0,0 }; void csBegin ( int proc ) { int competitor; if ( proc == 0 ) competitor = 1; else competitor = 0; wish[proc] = 1; right = competitor; while ( wish[competitor] && ( right == competitor ); } void csEnd ( int proc ) { wish[proc] = 0; } |
При входе в критическую секцию процесс заявляет о своем "желании" (строка 7) и отказывается от своего преимущественного права (строка 8). Процесс будет ожидать, если его конкурент заявил свое "желание" и имеет преимущественное право (строка 9). Если нет интереса конкурента или если независимо от интереса конкурента наш процесс имеет преимущественное право, то наш процесс входит в критическую секцию. Если наш процесс отказался от своего права в строке 8, то как же это право может к нему вернуться? Право нашего процесса может быть восстановлено конкурентом, когда последний тоже войдет в функцию csBegin своего кода и выполнит строку 8. При выходе из критической секции процесс просто снимает свой интерес и тогда его конкурент, возможно, ожидающий в строке 8, получает возможность выхода из цикла строки 9 по первой части условия.
Общие положительные свойства алгоритмов, основывающихся на неальтернативных переключателях (Деккера и Питерсона), следующие:
они корректны как для одно-, так и для многопроцессорных систем;
они либеральны, так как позволяют более быстрым процессам входить в свои критические секции чаще, чем медленным;
они не ограничивают количество обслуживаемых ими процессов;
они позволяют процессам сколь угодно долго задерживаться вне своей критической секции.
Но существуют и сложности:
решения не просты для понимания и ошибиться в их реализации очень легко;
процессы используют занятое ожидание при входе в критическую секцию.
8.4. Команда testAndSet и блокировки
Взаимное исключение при помощи переменных-переключателей базируется на атомарности обращений к памяти. Как мы показали выше, это делает решение универсальным как для одно-, так и для многопроцессорных систем. Но большинство архитектур компьютеров имеет в составе своей системы команд специальные команды с расширенной атомарностью обращений к памяти, при помощи которых можно реализовать взаимное исключение и быстрее, и проще. Общее название таких команд: testAndSet – проверить и установить. Действия такой команды могут быть описаны функцией:
int atomic testAndSet ( char *lock ) {
char var;
var = *lock;
*lock = 1;
return var;
}
(Здесь и далее мы, следуя правилам языка С, в котором параметры передаются по значению, вынуждены передавать в функции указатели, чтобы функции могли изменять значения параметров).
Команда проверяет (возвращает) значение некоторой переменной, а затем устанавливает ее значение в 1. Введенный нами описатель функции atomic показывает, что функция непрерываемая, во время ее выполнения никакой другой процесс не имеет доступа к той памяти, с которой работает функция (к переменной lock). Функция, как мы видим, выполняет два обращения к переменной lock, но оба они выполняются как одна транзакция.
Возможно, первые включения команд типа testAndSet в системы команд диктовались иными соображениями, но сейчас возможность выполнения подобных команд является обязательной для процессоров, претендующих на возможность использования в многопроцессорных комплексах. В микропроцессорах Intel-Pentium, например, имеются следующие команды, которые представляют собой "вариации на тему" testAndSet:
XCHG – перестановка;
BTS – проверка и установка бита;
BTR – проверка и сброс бита;
BTC – проверка бита и установка противоположного значения;
CMPXCHG – сравнить и заменить;
XADD – заменить и сложить.
Так, для реализации "канонической" функции testAndSet при помощи команды XCHG нужны две команды:
MOV al,1