- •Глава 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 14 15 16 17 18 19 |
static int right = 0; static char wish[2] = { 0,0 }; void csBegin ( int proc ) { int competitor; competitor = other ( proc ); while (1) { wish[proc] = 1; do { if ( ! wish[competitor] ) return; } while ( right != competitor ); wish[proc] = 0; while ( right == competitor ); } } void csEnd ( int proc ) { right = other ( proc ); wish[proc] = 0; } |
Алгоритм предусматривает, во-первых, общую переменную right для представления номера процесса, который имеет преимущественное (но не абсолютное) право на вход в критическую секцию. Во-вторых, массив wish, каждый элемент которого соответствует одному из процессов и представляет "желание" процесса войти в критическую секцию. Процесс заявляет о своем "желании" войти в секцию (строка 7). Если при этом выясняется, что процесс-конкурент не выставил своего "желания" (строка 9), то происходит возврат из функции, т.е. процесс входит в критическую секцию независимо от того, кому принадлежало преимущественное право на вход. Если же в строке 9 выясняется, что конкурент тоже выставил "желание", то проверяется право на вход (строка 10). Если право принадлежит нашему процессу, то повторяется проверка "желания" конкурента (строки 8 - 10), пока оно не будет отменено. Конкурент вынужден будет отменить свое "желание", потому что он в этой ситуации перейдет к строке 11, где процесс, не имеющий преимущественного права, должен это сделать. После отмены своего желания процесс ждет, пока преимущественное право не вернется к нему (строка 12), а затем вновь повторяет заявление "желания" и т.д. (строки 6 - 13). Таким образом, процесс в функции csBegin либо повторяет цикл 7 - 14, либо выходит из функции и входит в критическую секцию (10).
При выходе из критической секции (функция csEnd) процесс передает преимущественное право входа конкуренту (строка 16) и отказывается от своего желания (строка 17).
По собственному опыту признаем, что понимание этого алгоритма дается не очень просто. Рекомендуем для лучшего его понимания записать в две колонки две копии функции csBegin, соответствующие двум процессам, и промоделировать ход их параллельного выполнения с разными скоростями и разными сдвигами в фазах выполнения между процессами.
Приведем также обобщение алгоритма Деккера на N процессов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
static char wish[N+1] = { 0, ..., 0 }; static char claimant[N+1] = { 0, ..., 0 }; static int right = N; void csBegin ( int proc ) { int i; clainmant[proc] = 1; do { while ( right != proc) { wish[proc] = 0; if(!clainmant[right]) right=proc; } wish[proc] = 1; for (i = 0; i<N; i++ ) if ((i!=proc) && wish[i]) break; } while (i<N); } void csEnd ( int proc ) { right = N; wish[proc] = clainmant[proc] = 0; } |
Ограничимся здесь только общими замечаниями к этому алгоритму. Процессы нумеруются от 0 до N-1. Мы вводим два массива для переменных состояния, размеры массивов на 1 больше числа процессов. Последние элементы каждого из массивов соответствуют несуществующему N-му процессу, который используется как абстрактный "другой" процесс. Понятие "конкурент" здесь заменяется понятием "претендент" (clainmant). Процесс становится претендентом, входя в функцию csBegin (строка 6). В отличие от "желания" "претензия" процесса не снимается до тех пор, пока она не будет удовлетворена (строка 18). Если преимущественное право на вход в критическую секцию принадлежит другому процессу, но этот другой процесс не является претендентом, то наш процесс забирает это право себе (строки 7 - 11). При выполнении этих действий наш процесс, однако, отказывается от своего "желания", давая тем самым возможность участвовать в состязании за захват секции другим процессам (строка 9). Получив право, процесс заявляет о своем "желании" (строка 12). В последующем цикле for проверяются "желания" других процессов (строки 13 - 14). Если есть другие "желающие", то повторяется получение права и т.д. (строки 7 - 15). Если же в цикле for другие желающие не выявлены (строка 15), наш процесс входит в критическую секцию. При выходе из секции процесс сбрасывает свои "претензию" и "желание" (строка 18) и передает право несуществующему N-му процессу (строка 19).