В информатике свобода от помех — это метод доказательства частичной корректности параллельных программ с общими переменными. Логика Хоара была введена ранее для доказательства корректности последовательных программ. В своей докторской диссертации [1] (и работах, вытекающих из нее [ 2] [3] ) под руководством Дэвида Грайса Сьюзан Овицки распространила эту работу на параллельные программы.
Параллельное программирование использовалось с середины 1960-х годов для кодирования операционных систем как наборов параллельных процессов (см., в частности, Дейкстру. [4] ), но формального механизма для доказательства корректности не существовало. Рассуждения о чередующихся последовательностях выполнения отдельных процессов были сложными, подверженными ошибкам и не масштабировались. Свобода от помех применяется к доказательствам , а не к последовательностям выполнения; один показывает, что выполнение одного процесса не может мешать доказательству корректности другого процесса.
Целый ряд сложных параллельных программ были доказаны корректными с использованием свободы от помех, и свобода от помех обеспечивает основу для большей части последующей работы по разработке параллельных программ с общими переменными и доказательству их корректности. Статья Оуики-Гриса « Аксиоматическая техника доказательства для параллельных программ» [2] получила премию ACM Award 1977 года за лучшую статью в области языков и систем программирования. [5] [6]
Примечание. Лампорт [7] представляет похожую идею. Он пишет: «После написания первоначальной версии этой статьи мы узнали о недавней работе Оуики. [1] [2] «Его статья не привлекла столько внимания, как статья Оуики-Гриса, возможно, потому, что в ней использовались блок-схемы вместо текста программных конструкций, таких как оператор if и цикл while . Лампорт обобщал метод Флойда [8] , в то время как Оуики-Гриса обобщал метод Хоара. [9] По сути, все последующие работы в этой области используют текст, а не блок-схемы. Другое различие упоминается ниже в разделе о вспомогательных переменных.
Эдсгер В. Дейкстра ввел принцип невмешательства в EWD 117, «Программирование как человеческая деятельность», написанном около 1965 года. [10] Этот принцип гласит, что: Правильность целого может быть установлена, принимая во внимание только внешние спецификации (сокращенные спецификации по всему тексту) частей, а не их внутреннюю конструкцию. Дейкстра изложил общие шаги по использованию этого принципа:
Он привел несколько примеров этого принципа вне программирования. Но его использование в программировании является главной заботой. Например, программист, использующий метод (подпрограмму, функцию и т. д.), должен полагаться только на его спецификацию, чтобы определить, что он делает и как его вызывать, и никогда на его реализацию.
Спецификации программ написаны в логике Хоара , введенной сэром Тони Хоаром [9], как показано в спецификациях процессов S1 и S2 :
{до S1 } {до S2 } S1 S2 {после S1 } {до S2 }
Значение: Если выполнение Si в состоянии, в котором предварительное условие pre-Si является истинным, завершается, то по завершении постусловие post-Si является истинным.
Теперь рассмотрим параллельное программирование с общими переменными. Спецификации двух (или более) процессов S1 и S2 даны в терминах их предварительных и постусловий, и мы предполагаем, что даны реализации S1 и S2 , которые удовлетворяют их спецификациям. Но при параллельном выполнении их реализаций, поскольку они совместно используют переменные, может возникнуть состояние гонки ; один процесс изменяет общую переменную на значение, которое не ожидается в доказательстве другого процесса, поэтому другой процесс не работает так, как предполагалось.
Таким образом, нарушается принцип невмешательства Дейкстры.
В своей докторской диссертации 1975 года [1] по информатике , Корнелльский университет , написанной под руководством Дэвида Грайса , Сьюзен Овицки разработала понятие свободы помех . Если процессы S1 и S2 удовлетворяют свободе помех, то их параллельное выполнение будет работать так, как и планировалось. Дейкстра назвал эту работу первым значительным шагом на пути к применению логики Хоара к параллельным процессам. [11] Для упрощения обсуждений мы ограничиваем внимание только двумя параллельными процессами, хотя Овицки-Грис [2] [3] допускает больше.
Оуки-Грайс [2] [3] представил схему доказательства для тройки Хоара {P}S{Q }. Она содержит все детали, необходимые для доказательства корректности {P}S{Q } с использованием аксиом и правил вывода логики Хоара . (В этой работе используются оператор присваивания x: = e , операторы if-then и if-then-else , а также цикл while .) Хоар ссылался на схемы доказательства в своей ранней работе; для свободы от помех ее нужно было формализовать.
Схема доказательства для {P}S{Q } начинается с предусловия P и заканчивается постусловием Q. Два утверждения в фигурных скобках { и }, появляющиеся рядом друг с другом, указывают, что первое должно подразумевать второе.
Пример: Схема доказательства для {P} S {Q }, где S это:
x : = a; если e , то S1 , иначе S2
{P } {P1[x/a] } x : = a; {P1 } если e , то {P1 ∧ e } S1 {Q1 } иначе {P1 ∧ ¬ e } S2 {Q1 } {Q1 } {Q }
Должно выполняться условие P ⇒ P1[x/a] , где P1[x/a] обозначает P1 , где каждое вхождение x заменено на a . (В этом примере S1 и S2 являются базовыми операторами, такими как оператор присваивания, skip или await .)
Каждому утверждению T в схеме доказательства предшествует предусловие pre-T и за ним следует постусловие post-T , и {pre-T}T{post-T } должно быть доказуемо с использованием некоторой аксиомы или правила вывода логики Хоара. Таким образом, схема доказательства содержит всю информацию, необходимую для доказательства того, что {P}S{Q } является правильным.
Теперь рассмотрим два процесса S1 и S2 , выполняемых параллельно, и их характеристики:
{до S1 } {до S2 } S1 S2 {после S1 } {после S2 }
Доказательство того, что они работают параллельно, потребует ограничения их следующим образом. Каждое выражение E в S1 или S2 может ссылаться не более чем на одну переменную y , которая может быть изменена другим процессом во время вычисления E , и E может ссылаться на y не более одного раза. Аналогичное ограничение действует для операторов присваивания x: = E .
При таком соглашении единственным неделимым действием должна быть ссылка на память. Например, предположим, что процесс S1 ссылается на переменную y, в то время как S2 изменяет y . Значение, которое S1 получает для y, должно быть значением до или после того, как S2 изменяет y , а не каким-то ложным промежуточным значением.
Определение помехоустойчивости
Важным нововведением Овицки-Гриса было определение того, что означает, что утверждение T не мешает доказательству { P}S{Q }. Если выполнение T не может опровергнуть ни одно утверждение, данное в схеме доказательства {P}S{Q }, то это доказательство все еще остается в силе даже при одновременном выполнении S и T .
Определение . Утверждение T с предусловием pre-T не мешает доказательству {P}S{Q }, если выполняются два условия:
(1) {Q ∧ pre-T} T {Q }
(2) Пусть S' будет любым оператором внутри S , но не внутри оператора await (см. следующий раздел). Тогда {pre-S' ∧ pre-T} T {pre-S' }.
Прочитайте последнюю тройку Хоара следующим образом: если состояние таково, что могут быть выполнены как T , так и S' , то выполнение T не приведет к фальсификации pre-S' .
Определение . Схемы доказательства для {P1}S1{Q1 } и {P2}S2{Q2 } свободны от помех, если выполняется следующее. Пусть T — оператор await или присваивания (который не появляется в await ) процесса S1 . Тогда T не мешает доказательству {P2}S2{Q2 }. Аналогично для T процесса S2 и {P1}S1{Q1 }.
Для решения проблемы параллелизма были введены два оператора. Выполнение оператора cobegin S1 // S2 coend выполняет S1 и S2 параллельно. Он завершается, когда оба S1 и S2 завершаются.
Выполнение оператора await await B then S откладывается до тех пор, пока условие B не станет истинным. Затем оператор S выполняется как неделимое действие — оценка B является частью этого неделимого действия. Если два процесса ожидают одного и того же условия B , когда оно становится истинным, один из них продолжает ждать, пока другой продолжает.
Оператор await не может быть реализован эффективно и не предлагается для вставки в язык программирования. Вместо этого он предоставляет средства представления нескольких стандартных примитивов, таких как семафоры — сначала выразите операции семафора как await s , а затем примените описанные здесь методы.
Правила вывода для await и cobegin следующие:
ждать
{П ∧ Б} С {Q}/{P} ждем B , затем S {Q}
cobegin
{P1} S1 {Q1}, {P2} S2 {Q2}
без помех/{P1∧P2} cobegin S1//S2 coend {Q1∧Q2}
Вспомогательная переменная не встречается в программе, но вводится в доказательство корректности, чтобы упростить рассуждения — или даже сделать их возможными. Вспомогательные переменные используются только при присвоении вспомогательных переменных, поэтому их введение не изменяет программу для каких-либо входных данных и не влияет на значения переменных программы. Обычно они используются либо как счетчики программ, либо для записи истории вычислений.
Определение . Пусть AV — набор переменных, которые появляются в S' только в назначениях x: = E , где x находится в AV . Тогда AV — вспомогательный набор переменных для S' .
Поскольку набор вспомогательных переменных AV используется только в присваиваниях переменным в AV , удаление всех присваиваний им не изменяет корректность программы, и мы имеем правило вывода исключения AV :
{П} С' {Q}/{П} С {Q}
AV — это вспомогательный набор переменных для S' . Переменные в AV не встречаются в P или Q. S получается из S' путем удаления всех назначений переменным в AV .
Вместо использования вспомогательных переменных можно ввести в систему доказательства программный счетчик, но это усложнит систему доказательства.
Примечание: Апт [12] обсуждает логику Овицки-Гриса в контексте рекурсивных утверждений, то есть эффективно вычислимых утверждений. Он доказывает, что все утверждения в схемах доказательств могут быть рекурсивными, но это уже не так, если вспомогательные переменные используются только как счетчики программ, а не для записи истории вычислений. Лампорт в своей похожей работе [7] использует утверждения о позициях токенов вместо вспомогательных переменных, где токен на краю потоковой диаграммы сродни счетчику программ. Понятие переменной истории отсутствует. Это указывает на то, что подходы Овицки-Гриса и Лампорта не эквивалентны, когда ограничиваются рекурсивными утверждениями.
Овицки-Грайс [2] [3] в основном занимается частичной корректностью: {P} S {Q } означает: если S, выполненное в состоянии, в котором P истинно, завершается, то Q истинно для состояния по завершении. Однако Овицки-Грайс также приводит некоторые практические методы, которые используют информацию, полученную из доказательства частичной корректности, для вывода других свойств корректности, включая свободу от тупика, завершение программы и взаимное исключение.
Программа находится в тупике, если все процессы, которые не были завершены, выполняют операторы await и ни один из них не может продолжить работу, поскольку их условия await ложны. Оуики-Грайс предоставляет условия, при которых тупик не может возникнуть.
Овицки-Грайс представляет правило вывода для полной корректности цикла while . Оно использует связанную функцию, которая уменьшается с каждой итерацией и положительна, пока условие цикла истинно. Апт и др . [13] показывают, что это новое правило вывода не удовлетворяет свободе помех. Тот факт, что связанная функция положительна, пока условие цикла истинно, не был включен в тест на помехи. Они показывают два способа исправления этой ошибки.
Рассмотрим выражение:
{x=0}
cobegin await true then x: = x+1
// await true then x: = x+2
coend
{x=3}
Схема доказательства:
{х=0}
S: cobegin {x=0} {x=0 ∨ x=2} S1: await true then x : = x+1 {Q1: x=1 ∨ x=3} // {x=0} {x=0 ∨ x=1} S2: await true then x : = x+2 {Q2: x=2 ∨ x=3} coend
{(х=1 ∨ х=3) ∧ (х=2 ∨ х=3)}
{х=3}
Доказательство того, что S1 не мешает доказательству S2, требует доказательства двух троек Хоара:
(1) {(x=0 ∨ x=2) ∧ (x=0 ∨ x=1} S1 {x=0 ∨ x=1}
(2) {(x=0 ∨ x=2) ∧ (x=2 ∨ x=3} S1 {x=2 ∨ x=3}
Предварительное условие (1) сводится к x=0 , а предварительное условие (2) сводится к x=2 . Из этого легко видеть, что эти тройки Хоара выполняются. Для того, чтобы показать, что S2 не мешает доказательству S1 , требуются две подобные тройки Хоара .
Предположим, что S1 изменен с оператора await на присваивание x: = x+1 . Тогда схема доказательства не удовлетворяет требованиям, поскольку присваивание содержит два вхождения общей переменной x . Действительно, значение x после выполнения оператора cobegin может быть 2 или 3.
Предположим, что S1 изменен на оператор await await true then x: = x+2 , поэтому он такой же, как S2 . После выполнения S , x должен быть равен 4. Чтобы доказать это, поскольку два присваивания одинаковы, нужны две вспомогательные переменные: одна для указания того, был ли выполнен S1 ; другая — был ли выполнен S2 . Мы оставляем изменение в схеме доказательства читателю.
A. Findpos . Напишите программу, которая находит первый положительный элемент массива (если он есть). Один процесс проверяет все элементы массива на четных позициях массива и завершается, когда находит положительное значение или когда ничего не найдено. Аналогично, другой процесс проверяет элементы массива на нечетных позициях массива. Таким образом, этот пример имеет дело с циклами while . Он также не имеет операторов await .
Этот пример взят из работы Барри К. Розена. [14] Решение в Owicki-Gries, [2] полное с программой, планом доказательства и обсуждением свободы помех, занимает менее двух страниц. Свободу помех довольно легко проверить, поскольку есть только одна общая переменная. Напротив, статья Розена [14] использует Findpos как единственный, работающий пример в этой 24-страничной статье.
Схема обоих процессов в общей среде:
cobegin производитель: ... ожидание in-out < N затем пропуск ; добавить: b[in mod N]:= следующее значение; markin: in: = in+1; ... // потребитель: ... ожидание in-out > 0 затем пропуск ; удалить: это значение:= b[out mod N]; markout: out: = out+1;
коенд
...
B. Проблема потребителя/производителя с ограниченным буфером . Процесс-производитель генерирует значения и помещает их в ограниченный буфер b размером N ; процесс-потребитель удаляет их. Они выполняются с переменной скоростью. Производитель должен ждать, если буфер b полон; потребитель должен ждать, если буфер b пуст. В Овицки-Гриесе [2] показано решение в общей среде; затем оно встраивается в программу, которая копирует массив c[1..M] в массив d[1..M] .
Этот пример демонстрирует принцип сведения проверок помех к минимуму: поместите как можно больше в утверждение, которое инвариантно истинно везде в обоих процессах. В этом случае утверждение является определением ограниченного буфера и границ переменных, которые указывают, сколько значений было добавлено в буфер и удалено из него. Помимо самого буфера b , две общие переменные записывают количество значений , добавленных в буфер, и количество удаленных из буфера.
C. Реализация семафоров . В своей статье о системе мультипрограммирования THE [4] Дейкстра представляет семафор sem как примитив синхронизации: sem — это целочисленная переменная, на которую можно ссылаться только двумя способами, показанными ниже; каждый из них является неделимой операцией:
1. P(sem) : Уменьшить sem на 1. Если теперь sem < 0 , приостановить процесс и поместить его в список приостановленных процессов, связанных с sem .
2. V(sem) : Увеличить sem на 1. Если теперь sem ≤ 0 , удалить один из процессов из списка приостановленных процессов, связанных с sem , чтобы его динамическое выполнение снова стало допустимым.
Реализация P и V с использованием операторов await выглядит следующим образом:
P(sem): ожидание true затем начало sem: = sem-1; если sem < 0 тогда w[этот процесс]: = true конец ; ожидание ¬w[этот процесс] затем пропуск
V(sem): ожидание true затем начало sem: = sem+1; если sem ≤ 0 , то начало выбрать p так , чтобы w[ p ]; w[ p ]: = false конец конец
Здесь w — массив процессов, которые ждут, поскольку они были приостановлены; изначально w[p] = false для каждого процесса p . Можно изменить реализацию, чтобы всегда пробуждать самый долго приостановленный процесс.
D. Сборка мусора «на лету» . На летней школе в Марктобердорфе в 1975 году Дейкстра обсуждал сборщик мусора «на лету» как упражнение по пониманию параллелизма. Структура данных, используемая в обычной реализации LISP, представляет собой направленный граф, в котором каждый узел имеет не более двух исходящих ребер, каждое из которых может отсутствовать: исходящее левое ребро и исходящее правое ребро. Все узлы графа должны быть достижимы из известного корня. Изменение узла может привести к появлению недоступных узлов, которые больше не могут использоваться и называются мусором . Сборщик мусора «на лету» имеет два процесса: саму программу и сборщик мусора, задача которого — идентифицировать мусорные узлы и помещать их в свободный список, чтобы их можно было использовать снова.
Грайс чувствовал, что свободу вмешательства можно использовать для доказательства правильности сборщика мусора на лету. С помощью Дейкстры и Хоара он смог сделать презентацию в конце Летней школы, которая вылилась в статью в CACM. [15]
E. Проверка решения читатели/писатели с помощью семафоров . Куртуа и др. [16] используют семафоры, чтобы дать две версии проблемы читатели/писатели, без доказательства. Операции записи блокируют как чтение, так и запись, но операции чтения могут выполняться параллельно. Овицки [17] приводит доказательство.
Алгоритм Ф. Петерсона , решение проблемы взаимного исключения двух процессов, был опубликован Петерсоном в двухстраничной статье. [18] Шнайдер и Эндрюс приводят доказательство корректности. [19]
Изображение ниже, Ильи Сергея, изображает поток идей, которые были реализованы в логике, которая имеет дело с параллелизмом. В корне лежит свобода от помех. Файл CSL-Family-Tree (PDF)содержит ссылки. Ниже мы суммируем основные достижения.
{{cite journal}}
: CS1 maint: несколько имен: список авторов ( ссылка )