Основы многопоточного и распределенного программирования

       

Состояние, действие, история и свойства


Состояние параллельной программы состоит из значений переменных программы в неко­торый момент времени. Переменные могут быть явно определенными программистом или неявными (вроде программного счетчика каждого процесса), хранящими скрытую информа­цию о состоянии. Параллельная программа начинает выполнение в некотором исходном со­стоянии. Каждый процесс программы выполняется независимо, и по мере выполнения он проверяет и изменяет состояние программы.

Процесс выполняет последовательность операторов. Оператор, в свою очередь, реализу­ется последовательностью неделимых действий. Эти действия проверяют или изменяют со-

50                                                Часть 1. Программирование с разделяемыми переменными

стояние программы неделимым образом. Примерами неделимых действий являются непреры­ваемые машинные инструкции, которые загружают и сохраняют слова памяти.

Выполнение параллельной программы приводит к чередованию последовательностей не­делимых действий, производимых каждым процессом. Конкретное выполнение каждой про­граммы может быть рассмотрено как история s0—> s, —>... —>sn, где s0— начальное состояние. Переходы между состояниями осуществляются неделимыми действиями, изменяющими со­стояние. Историю также называют трассой последовательности состояний. Даже параллельное выполнение можно представить в виде линейной истории, поскольку параллельная реализация набора неделимых действий эквивалентна их выполнению в некотором последовательном по­рядке. Изменение состояния, вызванное неделимым действием, неразделимо, и, следовательно, на него не могут повлиять неделимые действия, производимые примерно в это же время.

Каждое выполнение параллельной программы порождает историю. Для всех, кроме самых тривиальных программ, число возможных историй громадно. Дело в том, что следующим в ис­тории может стать неделимое действие любого процесса. Следовательно, существует много спо­собов чередования действий, даже если выполнение программы всегда начинается в одном и том же исходном состоянии.
Кроме того, в каждом процессе обычно есть условные операторы, и, следовательно, возможны различные действия при различных изменениях в состоянии.

Цель синхронизации — исключить нежелаемые истории параллельной программы. Вза­имное исключение состоит в комбинировании неделимых действий, реализуемых непосред­ственно аппаратным обеспечением в виде последовательностей действий, которые называют­ся критическими секциями. Они должны быть неделимыми, т.е. их нельзя прервать действия-• ми других процессов, которые ссылаются на те же переменные. Синхронизация по условию (условная синхронизация) означает, что действие будет осуществлено, когда состояние будет удовлетворять заданному логическому условию. Обе формы синхронизации могут приоста­навливать процессы, ограничивая набор последующих неделимых действий.

Свойством программы называется атрибут, который является истинным при любой возмож--ной истории программы и, следовательно, при всех ее выполнениях. Есть два типа свойств: безопасность и живучесть. Свойство безопасности заключается в том, что программа никогда не попадает в "плохое" состояние (при котором некоторые переменные могут иметь нежелатель­ные значения). Свойство живучести означает, что программа в конце концов всегда попадает в "хорошее" состояние, т.е. состояние, в котором все переменные имеют желаемые значения.

Примером свойства безопасности является частичная корректность (правильность). Программа частично корректна (правильна), если правильно ее заключительное состояние (при условии, что программа завершается). Если программе не удается завершить выполне­ние, она может никогда не выдать правильный результат, но не существует такой истории, при которой программа завершается, не выдавая правильного результата. Завершимость — пример свойства живучести. Программа завершается, если завершается каждый цикл или вы­зов процедуры, т.е. длина каждой истории конечна. Тотальная (полная) корректность про­граммы — это свойство, объединяющее частичную корректность и завершимость: программа полностью корректна, если она всегда завершается, выдавая при этом правильный результат.



Взаимное исключение — это пример свойства безопасности в параллельной программе. При плохом состоянии два процесса такой программы одновременно выполняют действия в разных критических секциях. Возможность в конце концов войти в критическую секцию — пример свойства живучести в параллельной программе. В хорошем состоянии каждый про­цесс выполняется в своей критической секции.

Как же демонстрировать, что данная программа обладает желаемым свойством? Обычный подход состоит в тестировании, или отладке. Его можно охарактеризовать фразой "запусти программу и посмотри, что получится". Это соответствует перебору некоторых возможных историй программы и проверке их приемлемости. Недостаток такой проверки состоит в том, что каждый тест касается только одной истории выполнения, а ограниченное число тестов вряд ли способно продемонстрировать отсутствие плохих историй.

Глава 2. Процессы и синхронизация                                                                                             51

Второй подход— использование операторных рассуждений, которые можно назвать "исчер­пывающий анализ случаев" (перебираются все возможные истории выполнения программы). Для этого анализируются способы чередования неделимых действий процессов. К сожалению, в параллельной программе число возможных историй обычно очень велико (поэтому метод "изнурителен"5). Предположим, что параллельная программа содержит п процессов и каждый из них выполняет последовательность из m неделимых действий. Тогда число различных исто­рий программы составит (n-m) !/(m!n). Для программы из трех процессов, каждый из которых выполняет всего две неделимые операции, возможны 90 различных историй! (Числитель в фор­муле — это количество возможных перестановок из n-m действий. Но, поскольку каждый про­цесс выполняет последовательность действий, для него возможен только один порядок сле­дования m действий; знаменатель отбрасывает все варианты с неправильным порядком следо­вания.


Эта формула дает количество, равное числу способов перемешать п колод по m карт в каждой, при условии, что относительный порядок карт в каждой колоде сохраняется.)

Третий подход— применение утвердительных рассуждений (assertional reasoning); его можно назвать "абстрактный анализ". В этом методе формулы логики предикатов называют­ся утверждениями и используются для описания наборов состояний — например, всех со­стояний, у которых х > 0. Неделимые действия рассматриваются как предикатные преобра­зователи, поскольку они меняют состояние программы, удовлетворяющее одному предикату, на состояние, удовлетворяющее другому. Преимуществом данного подхода является ком­пактное представление состояний и их преобразований. Но еще важнее то, что он приводит к методу построения и анализа программ, согласно которому объем работы прямо пропор­ционален числу неделимых действий в программе.

Используем метод утверждений как инструмент построения и анализа решений многих нетривиальных задач. При разработке алгоритмов также будет применяться метод оператор­ных рассуждений. Наконец, многие программы этой книги были протестированы. Однако в результате тестирования можно только обнаружить наличие ошибок, а не гарантировать их отсутствие. Кроме того, параллельные программы очень сложны в тестировании и отладке, поскольку, во-первых, трудно остановить сразу все процессы и проверить их состояние, и, во-вторых, в общем случае каждое выполнение программы приводит к новой истории.


Содержание раздела