14.4. Распределенная взаимоблокировка
В главе 6, "Взаимоблокировка и голодание", взаимоблокировка была определена как постоянное блокирование ряда процессов, которые либо конкурируют, пытаясь получить доступ к системным ресурсам, либо обмениваются информацией друг с другом. Это определение справедливо как для изолированной, так и для распределенной системы. Проблема взаимоблокировки, как и проблема взаимоисключения, в распределенной системе является более сложной, чем в системе с общей памятью. Причинами этого усложнения является то, что ниодин узел не обладает точными сведениями о текущем состоянии всей системы, а также то, что передача сообщения от одного процесса другому сопровождается непредсказуемой по длительности временной задержкой.
В литературе рассматриваются два вида распределенных взаимоблокировок: возникающие в связи с распределением ресурсов и возникающие при обмене сообщениями. Взаимоблокировка, связанная с ресурсами, возникает тогда, когда процесс пытается получить доступ к ресурсам, например, объектам данных из базы данных или ресурсам ввода-вывода, которые находятся на сервере; при этом образуется группа процессов, каждый из которых запрашивает ресурс, которым обладает другой процесс из этой группы. Взаимоблокировка, связанная с обменом сообщениями, возникает в том случае, когда требующимися процессу ресурсами являются сообщения; при этом образуется группа процессов, каждый из которых ожидает сообщения от другого процесса из этой группы и ни один процесс не может отправить свое сообщение.
Взаимоблокировка, связанная с распределением ресурсов
В главе 6, "Взаимоблокировка и голодание", сказано, что взаимоблокировка в распределении ресурсов возникает только при соблюдении всех перечисленных условий.
- Взаимоисключение. Одновременно использовать ресурс может только один процесс.
- Удержание и ожидание. Процесс может удерживать выделенные ресурсы во время ожидания других ресурсов.
- Отсутствие перераспределения. Ресурс не может быть принудительно отобран у удерживающего его процесса.
- Циклическое ожидание. Существует замкнутая цепь процессов, каждый из которых удерживает как минимум один ресурс, необходимый процессу, следующему в цепи после него.
Цель алгоритма обработки взаимоблокировок состоит в том, чтобы либо предотвратить образование циклического ожидания, либо обнаружить, что оно возникло или может возникнуть. В распределенной системе ресурсы распределены по разным узлам. Доступ к ресурсам регулируется управляющими процессами на этих узлах. Любой отдельный управляющий процесс не обладает полными и свежими сведениями о глобальном состоянии системы и должен принимать решение, основываясь на локальной информации. Таким образом, в распределенной системе требуется новый алгоритм защиты от взаимоблокировок.
Одной из сложностей, возникающих при управлении распределенными взаимоблокировками, является явление фиктивной взаимоблокировки (phantom deadlock). Пример такой взаимоблокировки проиллюстрирован на рис. 14.11. Запись P1->P2->P3 означает, что процесс P1 приостановлен в ожидании ресурса, которым владеет процесс Р2, а процесс P2 приостановлен в ожидании ресурса, которым владеет процесс Р3. Пусть вначале процесс Р3 обладает ресурсом Ra, a процесс P1 — ресурсом Rb. Предположим, что процесс Р3 сначала генерирует сообщение, освобождающее ресурс Ra, а затем — сообщение с запросом ресурса Rb. Если процесс выявления циклов сначала получит первое сообщение, а затем второе, возникнет цепочка, изображенная на рис. 14.11,а. В противном случае будет зарегистрирована взаимоблокировка (рис. 14.11,б). На самом деле никакой взаимоблокировки не возникает; будет зарегистрирована ложная взаимоблокировка, вызванная отсутствием информации о глобальном состоянии.
Предотвращение взаимоблокировок
В распределенной среде можно использовать два метода предотвращения взаимоблокировок из тех, что обсуждались в главе 6, "Взаимоблокировка и голодание".
1. Возникновения циклического ожидания можно избежать, линейно упорядочив типы ресурсов. Если процессу выделены ресурсы типа R, то он может последовательно запрашивать только те ресурсы, номер типа которых следует после R. Основной недостаток этого метода состоит в том, что не всегда ресурсы можно запрашивать в том порядке, в котором они используются, поэтому ресурсы могут удерживаться процессом дольше, чем необходимо.
2. Условий удержания и ожидания можно избежать, потребовав, чтобы процесс запрашивал все необходимые ресурсы одновременно, и блокируя процесс до тех пор, пока такой запрос не сможет быть полностью выполнен в один момент времени. Такой подход неэффективен по двум причинам. Во-первых, процесс может длительное время ожидать одновременной доступности всех затребованных ресурсов, в то время как реально он мог бы работать и только с частью из них. Во-вторых, затребованные процессом ресурсы могут значительное время оставаться неиспользованными, и в течение этого времени они оказываются недоступными другим процессам.
При использовании обоих методов нужно, чтобы процесс заранее мог указать свои требования к ресурсам. Это не всегда возможно; примером является приложение типа базы данных, в которую можно динамически добавлять новые элементы. В качестве варианта подхода, при котором предварительные сведения такого рода не нужны, рассмотрим два алгоритма, предложенные в [ROSE78].
Оба они были разработаны в контексте работы с базами данных, и поэтому мы будем говорить не о процессах, а о транзакциях.
В предложенных методах используются временные метки. В каждой транзакции на протяжении ее времени жизни хранится временная метка, соответствующая времени ее создания. Таким образом, среди транзакций устанавливается строгий порядок. Если ресурс R, который используется транзакцией Т1, запрашивается другой транзакцией Т2, конфликт разрешается с помощью сравнения их временных меток. Соблюдение этого условия позволяет предотвратить образование циклического ожидания. Авторы предложили две разновидности этого базового метода и назвали их методом "ожидания-перезапуска" ("wait-die") и методом "прекращения-ожидания" ("wound-wait").
Предположим, что в текущий момент транзакция Т1 владеет ресурсом R, a транзакция Т2 генерирует запрос на этот ресурс. Алгоритм метода ожидания-перезапуска, который используется распределителем ресурсов, показан в листинге 14,2,а. Временные метки двух транзакций обозначены как е (Т1) и е (Т2). Если транзакция Т2 старше, она блокируется до тех пор, пока транзакция Т1 не освободит ресурс R — либо сгенерировав запрос, либо в результате того, что она будет "убита" при запросе другого ресурса. Если транзакция Т2 моложе, то она перезапускается с той же временной меткой, что и раньше.
Листинг 14.2. Методы предотвращения взаимоблокировок
if (e(T2) < е(Т1))
halt_T2 ('wait');
else
kill_T2 ('die');
а) Метод ожидания-перезапуска
if (e(T2) < е(Т1))
kill_Tl ('wound');
else
halt_T2 ('wait');
б) Метод прекращения-ожидания
Таким образом, при возникновении конфликта преимуществом обладает более старая транзакция. Благодаря тому что "убитая" транзакция восстанавливается со своей предыдущей временной меткой, она становится старше, и ее приоритет возрастает. Узлам не нужно знать о состоянии распределения всех ресурсов. Все, что нужно, — это временные метки транзакций, запрашивающих ресурс.
Как показано в листинге 14.2,5, метод прекращения-ожидания предоставляет немедленный доступ к запрашиваемому ресурсу старшей транзакции, удаляя владеющую этим ресурсом младшую транзакцию. В отличие от метода ожидания-перезапуска, транзакция не должна ждать, пока ей будет предоставлен ресурс, использующийся младшей транзакцией.
Избежание взаимоблокировок
Избежание взаимоблокировок — это метод динамического принятия решения о том, может ли предоставление данного ресурса по запросу привести к взаимоблокировке. В [SING94b] отмечено, что избежание распределенных взаимоблокировок является непрактичным по таким причинам.
1. Каждый узел должен следить за глобальным состоянием системы, что требует значительных накладных расходов на хранение и обмен информацией.
2. Процесс проверки безопасного глобального состояния должен быть взаимоисключающим. В противном случае вопрос о предоставлении ресурса двум различным ресурсам может рассматриваться на двух узлах, и они оба одновременно могут принять решение о том, что удовлетворение запроса является безопасным. В результате возникнет взаимоблокировка.
3. В распределенной системе с большим количеством процессов и ресурсов проверка безопасности состояния приводит к значительным накладным расходам на обработку запросов.
Выявление взаимоблокировок
При методе выявления взаимоблокировок процессам позволено получать незанятые ресурсы по первому требованию, а наличие взаимоблокировок определяется при их возникновении. Если обнаружена взаимоблокировка, выбирается один из составляющих (constituent) процессов, к которому предъявляется требование освободить ресурсы, необходимые для снятия взаимоблокировки.
При выявлении распределенных взаимоблокировок возникают трудности, состоящие в том, что каждый узел обладает сведениями только о своих ресурсах, в то время как взаимоблокировка может включать в себя распределенные ресурсы. В зависимости от того, как управляется система — централизовано, иерархически или распределение, — можно использовать различные подходы (см. табл. 14.1).
При централизованном управлении ответственность за выявление взаимоблокировок возлагается на один узел. Все сообщения с запросами и освобождениями ресурсов отправляются центральному процессу, а также процессу, контролирующему данный ресурс. Поскольку центральный процесс обладает всеми необходимыми сведениями, он в состоянии определить наличие взаимоблокировки. Такой подход требует передачи большого количества сообщений; он также уязвим по отношению к отказу центрального узла. Кроме того, не исключена возможность выявления фиктивных взаимоблокировок.
При иерархическом управлении узлы организованы в древовидную структуру, в которой один из них выступает в роли корня дерева. На каждом узле, не являющемся листом, собрана информация о распределении ресурсов между всеми зависимыми от него узлами. Это позволяет выявлять взаимоблокировки, возникающие на более низких по отношению к корневому узлу уровнях. Другими словами, взаимоблокировка, в которой участвует какое-то множество ресурсов, будет выявлена тем узлом, который является общим "предком" всех узлов, ресурсы которых принимают участие в конфликте.
При распределенном управлении все процессы выявляют взаимоблокировку сообща. Вообще говоря, это означает необходимость интенсивного обмена информацией, включающей временные метки; в результате возникают значительные накладные расходы. В [RAYN88] можно найти несколько ссылок на алгоритмы распределенного управления, а в [DATT90] приводится подробное изучение одного из таких подходов.
Таблица 14.1. Стратегии выявления распределенных взаимоблокировок
Централизованные алгоритмы |
Иерархические алгоритмы |
Распределенные алгоритмы |
|||
Достоинства |
Недостатки |
Достоинства |
Недостатки |
Достоинства |
Недостатки |
• Алгоритмы просты для понимания и реализации • Центральный узел обладает полной информацией и может оптимально разрешать взаимоблокировки |
• Значительные накладные рас- ходы на обмен информацией; каждый узел должен пересылать централь- ному узлу ин- формацию о своем состоянии • Центральный узел является уязвимым звеном
|
• Неуязвим по отношению к отказу одного узла • Достаточность малых действий при относительной локализации потенциальных взаимоблокировок |
Трудность настройки системы так, чтобы большинство потенциальных взаимоблокировок были локализованы; в противном случае этот подход может приводить к большим накладным расходам |
• Неуязвим по отношению к отказу одного из узлов • Ни один узел не перезагружен действиями по разрешению взаимоблокировок |
• Разрешение взаимоблокировок может оказаться сложным в силу того, что одна и та же взаимоблокировка может быть обнаружена несколькими узлами, которые могут не знать друг о друге • Из-за временных вопросов усложнена разработка алгоритмов |
Приведем пример алгоритма, предназначенного для выявления распределенных взаимоблокировок ([DATT92, JOHN91]). Этот алгоритм работает с распределенной базой данных, в которой на каждом узле поддерживается некоторая часть базы данных; транзакции могут инициироваться каждым узлом. В каждой транзакции может быть не больше одного запроса ресурса. Если транзакция нуждается в нескольких объектах данных, второй объект данных можно запрашивать только после того, как будет предоставлен первый объект данных.
С каждым объектом данных i на узле связаны два параметра: уникальный идентификатор D, и переменная Locked__by (Di), Эта переменная имеет нулевое значение, если объект данных не заблокирован ни одной транзакцией. В противном случае ее значение равно идентификатору блокирующей транзакции.
С каждой транзакцией j на узле связано четыре параметра:
- уникальный идентификатор Тj;
- переменная Held_by(Тj), значение которой равно нулю, если транзакция Тj находится в состоянии выполнения или готовности; в противном случае ее значение равно идентификатору транзакции, которая удерживает объект данных, требующийся транзакции Тj;
- переменная Wait_for (Тj), значение которой равно нулю, если транзакция Тj не ожидает никаких других транзакций; в противном случае ее значение равно идентификатору транзакции, которая находится во главе упорядоченного списка блокированных транзакций;
- очередь Request_Q(Тj), в которой содержатся все необработанные запросы объектов данных, удерживаемых транзакцией Тj; каждый элемент очереди имеет вид (Тk, Dk, где Тk — запрашивающая транзакция, a Dk — объект данных, удерживаемый транзакцией Тj.
Предположим, например, что транзакция Т2 ожидает предоставления объекта данных, закрепленного за транзакцией Т1 которая, в свою очередь, ожидает предоставления объекта данных, закрепленного за транзакцией Т0. При этом параметры имеют такие значения:
Транзакция |
Wait_for |
Held_by |
Request Q |
Т0 |
0 |
0 |
T1 |
T1 |
Т0 |
Т0 |
Т2 |
Т2 |
Т0 |
Т1 |
0 |
Этот пример дает возможность понять разницу между параметрами Wait_for (Тi) и Held_by (Тi) . Ни один процесс не может продолжаться, пока Т0 не освободит объект данных, нужный для T1 который после этого сможет выполнить необходимые действия с этим объектом данных и освободить его для Т2.
В листинге 14.3 приведен алгоритм, использующийся для выявления взаимоблокировок. Когда транзакция делает запрос на блокировку объекта данных, связанный с этим объектом данных обслуживающий процесс либо удовлетворяет запрос, либо отказывает в его удовлетворении. Если требуемый ресурс не предоставляется, обслуживающий процесс возвращает идентификатор транзакции, удерживающей этот объект данных.
Листинг 14.3. Алгоритм выявления распределенных взаимоблокировок
/* Получение объектом данных dj запроса lock_request (Тi) */
if (Locked_by(Dj) == null)
send(granted);
else {
send (отказ транзакции) для Тi;
send (Locked_by(Dj) ) для Ti
}
/* Транзакция Ti выполняет блокирующий запрос объекта данных Dj */
send (lock_request(Tj ) для Dj;
ожидание предоставления/отказа;
if (granted)
{
Locked_by(Dj) = Ti
Held_by(Ti) = 0;
}
else /* Считаем, что Dj используется транзакцией Tj */
{
Held_by(Ti) =Tj;
Enqueue (Ti, Request_Q{Tj));
if (Wait_for(Tj) == null)
Wait_for (Ti) = Tj;
else
Wait_for(Ti) = Wait_for(Tj) ;
update(Wait_for(Ti) , Request_Q(Ti) ;
}
/* Получение транзакцией Tj обновляющего сообщения */
if (Wait_for (Tj) != Wait_for (Ti))
Wait_for{Tj) = Wait__for(Ti);
if (intersect(Wait_for(Tj) , Request_Q(Tj) ) = null)
update(Wait_for(Ti) , Request_Q(Tj) ) ;
else
{
Объявление взаимоблокировки
/* начало разрешения взаимоблокировки */
/* выбирается Tj, которая должна быть прекращена */
/* Тj освобождает все удерживаемые ею объекты данных */
send_clear (Тj , Held_by (Tj) ) ;
каждый освобожденный объект данных Di предоставляется транзакции Tk, находящейся во главе очереди Request_Q(Tj) ;
fог(каждой транзакции Тn из очереди Request_Q(Tj) ,
запрашивающей объект данных, которых находится в Тj)
{
Enqueue(Tn, Request_Q(Tk) ) ;
}
}
/* Получение транзакцией Tk сообщения clear(Tj, Tk) */
удаление транзакции Tj из очереди Request_Q (Tk) ;
Когда запрашивающая транзакция получает ответ о том, что ее запрос будет удовлетворен, она блокирует объект данных. В противном случае запрашивающая транзакция обновляет свою переменную Held__by, присваивая ей идентификатор транзакции, удерживающей объект данных, и добавляет свой идентификатор в очередь транзакций Request_Q. Транзакция также обновляет переменную Wait_for, присваивая ей либо идентификатор транзакции, удерживающей объект данных (если эта транзакция не находится в ожидании), либо тот идентификатор транзакции, который хранится в переменной Wait_for удерживающей транзакции. В этом случае переменной Wait_for присваивается значение транзакции, которая находится в начале цепочки запирающих транзакций. Наконец, запрашивающая транзакция рассылает всем другим транзакциям обновляющее сообщение со своим параметром Request_Q, чтобы изменить значения всех подлежащих изменению переменных Wait_for.
Получив обновляющее сообщение, транзакция обновляет свою переменную Wait_for. Это означает, что ожидаемая ею транзакция сейчас заблокирована другой. Затем выполняются действия по выявлению взаимоблокировки, в процессе которых проверяется, ожидает ли транзакция какую-то другую, которая, в свою очередь, ждет первую. Если это условие не выполняется, транзакция передает обновляющее сообщение дальше. Если же условие выполняется, транзакция отправляет той транзакции, которая удерживает нужный ей объект данных, сообщение об освобождении своих объектов, предоставляя каждый объект данных транзакции, которая согласно очереди Request_Q запросила его первой. Оставшиеся запросы помещаются в очередь новой транзакции.
На рис. 14.12 приведен пример работы описанного алгоритма. Когда транзакция Т0 запрашивает объект данных, содержащийся в транзакции Т3, возникает цикл. Транзакция Т0 отправляет обновляющее сообщение, которое передается от T1 к Т2 и от Т2 к Т3. В этот момент транзакция Т3 обнаруживает, что пересечение ее переменных Wait_for и Request_Q отлично от нуля. Транзакция Т3 отправляет транзакции Т2 сообщение об освобождении своих объектов данных, вследствие чего Т3 удаляется из очереди Request_Q (Т2), и освобождает свои объекты, активизируя транзакции Т4 и Тб.
Транзакция |
Wait_for |
Held by |
Request Q |
Т0 |
0 |
0 |
T1 |
T1 |
Т0 |
Т0 |
Т2 |
Т2 |
Т0 |
T1 |
Т3 |
Т3 |
Т0 |
Т2 |
Т4, Т6 |
Т4 |
Т0 |
Т3 |
Т5 |
Т5 |
Т0 |
Т4 |
0 |
Т6 |
Т0 |
Т3 |
0 |
а) Состояние системы до запроса |
|||
Транзакция |
Wait_for |
Held_by |
Request Q |
Т0 |
Т0 |
Та |
T1 |
T1 |
Т0 |
Т0 |
Т2 |
Т2 |
Т0 |
T1 |
Т3 |
Т3 |
Т0 |
Т2 |
Т4, Т6, Т0 |
Т4 |
Т0 |
Т3 |
Т5 |
Т5 |
Т0 |
Т4 |
0 |
Т6 |
Т0 |
Т3 |
0 |
б) Состояние системы после того, как запрос транзакции Т0 будет получен транзакцией Тз
Взаимоблокировки, возникающие при обмене сообщениями
Взаимное ожидание
При обмене сообщениями взаимоблокировки возникают, если есть группа процессов, каждый из которых ожидает сообщения от другого процесса, являющегося членом этой группы, и не осталось ни одного сообщения в каналах.
Чтобы проанализировать эту ситуацию подробнее, определим понятие множества зависимости (dependence set — DS) процесса. Для процесса Р,, который не может выполняться из-за ожидания сообщения, множество DS(PJ состоит из всех процессов, от которых процесс Р; ждет сообщения. Обычно процесс Р( сможет продолжить свою работу после получения любого из сообщений. Возможна также ситуация, когда процесс может продолжать работу только после получения всех ожидаемых сообщений. Однако наиболее распространенной является первая ситуация, поэтому в данной книге рассматривается именно она.
Опираясь на приведенное выше определение, можно сформулировать определение взаимоблокировки множества процессов S. Взаимоблокировка возникает при таких условиях.
1. Все процессы множества S находятся в ожидании сообщений.
2. S содержит множества зависимости всех процессов, входящих в S.
3. В каналах нет ни одного сообщения, передаваемого от одного члена множества S другому.
Все процессы множества S являются заблокированными, так как не может быть получено ни одно сообщение, благодаря которому хотя бы один процесс мог продолжить работу.
Разницу между взаимоблокировкой, возникающей при обмене сообщениями, и взаимоблокировкой, возникающей при распределении ресурсов, можно пояснить с помощью графов. Взаимоблокировка ресурсов возникает тогда, когда в графе зависимости процессов образуется замкнутый цикл. В случае взаимоблокировки ресурсов один процесс зависит от другого, если последний обладает нужным ему ресурсом. Условие взаимоблокировки сообщений состоит в том, что все последующие элементы любого входящего в множество S члена сами входят в этот множество.
Это положение проиллюстрировано на рис. 14.13. На рис. 14.13,а процесс P1 ожидает сообщения либо от процесса Р2, либо от процесса Р5; процесс Р5 не ожидает никаких сообщений, поэтому он может отправить сообщение процессу P1, тем самым выводя его из состояния ожидания. В результате звенья (P1, Р5) и (Р1,Р2) удаляются. На рис. 14.13,б добавлена еще одна зависимость: процесс Р5 ожидает сообщения от процесса Р2, который ждет сообщения от процесса Р3, ожидающего сообщения от процесса Р1 , который, в свою очередь, ждет сообщения от процесса Р2. В результате возникает взаимоблокировка.
Проблему взаимоблокировки сообщений, как и проблему взаимоблокировки ресурсов, можно решить, предотвращая ее возникновение или выявляя образование взаимоблокировки. Некоторые примеры можно найти в [RAYN88].
Недоступность буферов сообщений
Еще одна причина, по которой в системе с обменом сообщениями может возникнуть взаимоблокировка, связана с распределением буферов, предназначенных для хранения передаваемых сообщений. Этот вид взаимоблокировок хорошо известен в сетях с коммутацией пакетов данных. Сначала разберем эту проблему в контексте сети передачи данных, а затем рассмотрим ее с точки зрения распределенной операционной системы.
Самым простым видом взаимоблокировки в сети передачи данных является непосредственная блокировка хранения и передачи сообщений (store-and-forward deadlock). Она может возникнуть в том случае, когда узел пакетной коммутации использует единый буферный пул, буфера которого по мере необходимости предоставляются пакетам. На рис. 14.14,а показана ситуация, в которой все буферное пространство на узле А занято пакетами, предназначенными для узла В. То же справедливо и для узла В. Ни один из узлов не может принять ни одного пакета, поскольку их буфера заполнены. Таким образом, ни один из них не может передать или получить сообщение.
Непосредственную блокировку хранения и передачи сообщений можно предотвратить, не позволяя всем буферам заполняться сообщениями, предназначенными для одного узла. Этого можно достичь, используя раздельные буфера фиксированного размера для каждой связи. Если сообщения, предназначенные для одного узла, не смогут занять все буферное пространство, взаимоблокировка не возникнет даже при использовании общего буферного пула.
На рис. 14.14,б проиллюстрирована более сложная разновидность взаимоблокировки — косвенная блокировка хранения и передачи сообщений. Она возникает, если образуется замкнутый цикл узлов, на каждом из которых очередь сообщений, передаваемых на соседний узел, до отказа заполнена пакетами, предназначенными для узла, находящегося за соседним. Одним из простых способов предотвращения взаимоблокировки подобного типа является использование структурированного буферного пула (рис. 14.15). Такие буфера имеют иерархическую организацию. Использование пула памяти на нулевом уровне не ограничивается никакими правилами; там может храниться любой входящий пакет. Буфера, которые находятся на уровнях с первого по N-n (где N — это максимальное количество промежуточных узлов при передаче пакета по сети), резервируются следующим образом: буфера на уровне k зарезервированы для пакетов, которые до этого подверглись не менее k транзитным пересылкам. Таким образом, при большой загрузке сети буфера заполнены от нулевого до N-го уровня. Если заполнены все уровни до k-гo включительно, то не принимается ни одно входящее сообщение, прошедшее менее k транзитных пересылок. Можно показать (см. [GOPA85]), что при соблюдении такой стратегии удается избежать как прямых, так и косвенных взаимоблокировок.
С описанной выше проблемой взаимоблокировок можно столкнуться в контексте коммуникационной архитектуры, обычно на сетевом уровне модели OSI. Проблема такого же рода может возникнуть и в распределенной операционной системе, в которой межпроцессное взаимодействие происходит с помощью обмена сообщениями. В частности, если операция отправки не является блокирующей, то для хранения исходящих сообщений нужен буфер. Буфер, который используется для хранения сообщений, отправленных процессом X процессу Y, можно рассматривать как канал обмена информацией между процессами X и Y. Если емкость этого канала ограничена (конечным размером буфера), то операция отправки сообщения может привести к приостановке процесса. Другими словами, если емкость буфера — п сообщений, и в процессе передачи (т.е. еще не получены адресатом) находятся п сообщений, то попытка отправить еще одно сообщение приведет к блокированию процесса до тех пор, пока в буфере не освободится место.
На рис. 14.16 проиллюстрировано, как может возникнуть взаимоблокировка при использовании конечного числа каналов. На рисунке показаны два канала, каждый из которых обладает емкостью в четыре сообщения. Если в каждом из каналов (из X в Y и из Y в X) находится ровно по четыре сообщения и оба узла попытаются передать еще по одному сообщению, прежде чем получат хотя бы одно имеющееся, то оба они приостановят свою работу, в результате чего возникнет взаимоблокировка.
Если есть возможность ограничить сверху количество сообщений, передаваемых между каждой парой процессов системы, то очевидная стратегия предотвращения взаимоблокировок состоит в том, чтобы предоставить каждому каналу необходимое количество слотов в буфере. Этот способ может оказаться чрезмерно расточительным и, конечно же, требует некоторых предварительных сведений. Если требования нельзя предугадать или если выделение буферов, размер которых оценивается по верхней границе, является слишком расточительным, для оптимизации объема буферов нужно применять методы оценки. Можно показать, что в общем случае эта проблема является неразрешимой. Некоторые эвристические методы решения задачи предложены в [BARB90].
