Опишите подходы формальной верификации программ (model checking, доказательства с пред- и постусловиями, SMT‑решатели) и примените один из них для доказательства отсутствия гонок и сохранения инвариантов в простом псевдокоде с двумя потоками
Кратко — три подхода: - Model checking: строится конечная модель (граф состояний) программы и систематически перебираются все состояния/переходы; проверяются свойства (LTL/CTL) — отлично для конечных/ограниченных систем, обнаруживает контрпримеры. - Доказательства с предусловиями/постусловиями (Hoare‑логика, rely/guarantee, Separation Logic): формулируются утверждения вида {P} C {Q}\{P\}\;C\;\{Q\}{P}C{Q}. Для конкурентности вводят инварианты и правила для критических секций/замков; даёт структурированное, понятное доказательство. - SMT‑решатели: сводят свойства и поведение программы к формуле в логике (обычно баг‑поиск/инваринты), SMT проверяет выполнимость; подходят для проверки арифметики, указателей и сложных условий, часто в сочетании с инвариантным выводом или абстрактной интерпретацией. Применение: доказательство отсутствия гонок и сохранения инварианта для простейшего примера с двумя потоками, методом Hoare‑логики с замком. Псевдокод (разделяемые переменные: xxx, LLL; L=0L=0L=0 — свободен, L=1L=1L=1 — захвачен): Thread1: acquire(L); x := x + 1; release(L); Thread2: acquire(L); x := x + 2; release(L); Начальное состояние: x=0, L=0x = 0,\; L = 0x=0,L=0. Инвариант, который хотим сохранить: I: x≥0.\;I:\; x \ge 0.I:x≥0. Допущения: операции присваивания и изменения LLL выполняются атомарно; acquire — выполняется как атомарная проверка/установка LLL (т.е. позволяется только при L=0L=0L=0 и делает L:=1L:=1L:=1). Доказательство по шагам (Hoare‑тройки): 1) Захват замка (acquire). Правило присваивания даёт: {L=0∧I} L:=1 {L=1∧I}.\{L = 0 \wedge I\}\; L := 1\; \{L = 1 \wedge I\}.{L=0∧I}L:=1{L=1∧I}.
То есть захват не разрушает инвариант III (он касается только xxx). 2) Критическая секция потока 1: {L=1∧I} x:=x+1 {L=1∧x≥1}.\{L = 1 \wedge I\}\; x := x + 1\; \{L = 1 \wedge x \ge 1\}.{L=1∧I}x:=x+1{L=1∧x≥1}.
Из x≥0x \ge 0x≥0 после x:=x+1x := x+1x:=x+1 следует x≥1x \ge 1x≥1, значит III сохраняется: x≥1⇒x≥0x \ge 1 \Rightarrow x \ge 0x≥1⇒x≥0. 3) Освобождение замка: {L=1∧I} L:=0 {L=0∧I}.\{L = 1 \wedge I\}\; L := 0\; \{L = 0 \wedge I\}.{L=1∧I}L:=0{L=0∧I}.
Освобождение тоже не трогает xxx. Аналогично для потока 2: {L=1∧I} x:=x+2 {L=1∧x≥2}\{L = 1 \wedge I\}\; x := x + 2\; \{L = 1 \wedge x \ge 2\}{L=1∧I}x:=x+2{L=1∧x≥2}, и затем release сохраняет III. Взаимное исключение (отсутствие гонок): по предположению acquire реализован как атомический переход, допускающий захват только при L=0L=0L=0, и делает L:=1L:=1L:=1. Следовательно две критические секции не могут выполняться одновременно: не существует состояния, в котором оба потока имеют L=1L=1L=1. Поскольку все операции над xxx выполняются только внутри критической секции (когда L=1L=1L=1 у владельца), конкурентного доступа к xxx без синхронизации не происходит — значит гонок данных нет. Итог: при допущениях об атомарности acquire/release и приведённых Hoare‑трюках, каждый шаг сохраняет инвариант x≥0 \;x \ge 0\;x≥0 и замок обеспечивает взаимное исключение, следовательно отсутствуют гонки и инвариант сохраняется во всей параллельной системе. Короткое замечание о других методах: тот же пример можно формализовать для model checking — моделью будет кортеж (x,L,s1,s2)(x,L,s_1,s_2)(x,L,s1,s2) и проверить, что во всех достижимых состояниях x≥0x \ge 0x≥0 и не существует состояния с одновременным входом в критические секции; или с помощью SMT вывести инвариант x≥0 \;x \ge 0\;x≥0 автоматическим образом.
- Model checking: строится конечная модель (граф состояний) программы и систематически перебираются все состояния/переходы; проверяются свойства (LTL/CTL) — отлично для конечных/ограниченных систем, обнаруживает контрпримеры.
- Доказательства с предусловиями/постусловиями (Hoare‑логика, rely/guarantee, Separation Logic): формулируются утверждения вида {P} C {Q}\{P\}\;C\;\{Q\}{P}C{Q}. Для конкурентности вводят инварианты и правила для критических секций/замков; даёт структурированное, понятное доказательство.
- SMT‑решатели: сводят свойства и поведение программы к формуле в логике (обычно баг‑поиск/инваринты), SMT проверяет выполнимость; подходят для проверки арифметики, указателей и сложных условий, часто в сочетании с инвариантным выводом или абстрактной интерпретацией.
Применение: доказательство отсутствия гонок и сохранения инварианта для простейшего примера с двумя потоками, методом Hoare‑логики с замком.
Псевдокод (разделяемые переменные: xxx, LLL; L=0L=0L=0 — свободен, L=1L=1L=1 — захвачен):
Thread1:
acquire(L);
x := x + 1;
release(L);
Thread2:
acquire(L);
x := x + 2;
release(L);
Начальное состояние: x=0, L=0x = 0,\; L = 0x=0,L=0.
Инвариант, который хотим сохранить: I: x≥0.\;I:\; x \ge 0.I:x≥0.
Допущения: операции присваивания и изменения LLL выполняются атомарно; acquire — выполняется как атомарная проверка/установка LLL (т.е. позволяется только при L=0L=0L=0 и делает L:=1L:=1L:=1).
Доказательство по шагам (Hoare‑тройки):
1) Захват замка (acquire). Правило присваивания даёт:
{L=0∧I} L:=1 {L=1∧I}.\{L = 0 \wedge I\}\; L := 1\; \{L = 1 \wedge I\}.{L=0∧I}L:=1{L=1∧I}. То есть захват не разрушает инвариант III (он касается только xxx).
2) Критическая секция потока 1:
{L=1∧I} x:=x+1 {L=1∧x≥1}.\{L = 1 \wedge I\}\; x := x + 1\; \{L = 1 \wedge x \ge 1\}.{L=1∧I}x:=x+1{L=1∧x≥1}. Из x≥0x \ge 0x≥0 после x:=x+1x := x+1x:=x+1 следует x≥1x \ge 1x≥1, значит III сохраняется: x≥1⇒x≥0x \ge 1 \Rightarrow x \ge 0x≥1⇒x≥0.
3) Освобождение замка:
{L=1∧I} L:=0 {L=0∧I}.\{L = 1 \wedge I\}\; L := 0\; \{L = 0 \wedge I\}.{L=1∧I}L:=0{L=0∧I}. Освобождение тоже не трогает xxx.
Аналогично для потока 2:
{L=1∧I} x:=x+2 {L=1∧x≥2}\{L = 1 \wedge I\}\; x := x + 2\; \{L = 1 \wedge x \ge 2\}{L=1∧I}x:=x+2{L=1∧x≥2},
и затем release сохраняет III.
Взаимное исключение (отсутствие гонок): по предположению acquire реализован как атомический переход, допускающий захват только при L=0L=0L=0, и делает L:=1L:=1L:=1. Следовательно две критические секции не могут выполняться одновременно: не существует состояния, в котором оба потока имеют L=1L=1L=1. Поскольку все операции над xxx выполняются только внутри критической секции (когда L=1L=1L=1 у владельца), конкурентного доступа к xxx без синхронизации не происходит — значит гонок данных нет.
Итог: при допущениях об атомарности acquire/release и приведённых Hoare‑трюках, каждый шаг сохраняет инвариант x≥0 \;x \ge 0\;x≥0 и замок обеспечивает взаимное исключение, следовательно отсутствуют гонки и инвариант сохраняется во всей параллельной системе.
Короткое замечание о других методах: тот же пример можно формализовать для model checking — моделью будет кортеж (x,L,s1,s2)(x,L,s_1,s_2)(x,L,s1 ,s2 ) и проверить, что во всех достижимых состояниях x≥0x \ge 0x≥0 и не существует состояния с одновременным входом в критические секции; или с помощью SMT вывести инвариант x≥0 \;x \ge 0\;x≥0 автоматическим образом.