#arrays #concurrency #task #ada
#массивы #параллелизм #задача #ada
Вопрос:
Какой хороший способ связать индексированную задачу с соответствующим индексированным защищенным типом в SPARK?
Для уточнения рассмотрим эту настройку:
subtype Thread_Range is Natural range 1..n;
protected type P is ... end P;
p: array(Thread_Range) of P;
Для каждой p(i)
я хотел бы иметь задачу, t(i)
которая отслеживает p(i)
и, когда она будет готова, обрабатывает ее. Я могу довольно легко выполнить эту работу в Ada, но SPARK с Ravenscar более требователен. Я попробовал два подхода, которые, похоже, работают нормально, когда я их запускаю:
- Укажите
T
Integer
дискриминант, затем создайте экземплярT(i);
для каждогоi
, но это становится обременительным при не очень большихi
размерах.
task type T(which: Integer);
t1: T(1);
t2: T(2);
...
- Добавьте
is_not_monitored
функцию иset_monitor
процедуру вP
. Создайте массив задач без дискриминанта. Приt(i)
запуске он назначает себя для мониторинга первомуp(j)
обнаруженному, которому еще не был назначен монитор.
task type T;
task body T is
which: Integer;
available: Boolean;
begin
for i in Thread_Range loop
available := p(i).is_not_monitored;
if available then
p(i).set_monitor;
which := i;
end if;
end loop;
-- what the task does with p(i) follows
end T;
t: array(Thread_Range) of T;
Второй вариант мне нравится больше, но ненамного. В любом случае, SPARK «Prove» ворчит по поводу потенциальной гонки данных, и я понимаю почему (хотя я не уверен, что это действительно связано с этим).
Отсюда и вопрос.
Комментарии:
1. Не могли бы вы добавить еще один защищенный объект с помощью одной процедуры
Get_Next_Available_Index(Next : out Thread_Range)
?2. Это имеет смысл, и мне это нравится больше, чем другие варианты. Я попробую это и дам вам знать.
3. Странно. Программа работает нормально, но «Исследовать» и «Доказать» мне не дают
GNAT BUG DETECTED
. Я думаю, это один из способов заставить SPARK перестать ворчать…4. (если быть точным, ошибка в
Constraint_Error erroneous memory access
)5. @egilhh Размышляя о вашем ответе (и реализовав его в другой программе, поскольку SPARK решил завершить работу с моим оригиналом): не эквивалентно ли это второму подходу выше? Мне кажется, что он просто перемещается
is_not_monitored
и (возможно) флаги монитора к другому защищенному объекту. Черт возьми, SPARK, кажется, доволен вторым подходом, когда я размещаю его в новом проекте.
Ответ №1:
Это не приводит к блокировке gnatprove.
И я думаю, что основное отличие от вашего варианта 2 заключается в том, что Claim
проверяет, возможно ли утверждение, и, если да, выполняет утверждение в одном защищенном вызове.
Но я не совсем понимаю, как доказать, что цикл Claim
в T
завершается с Ps (J)
утверждением. Я попытался поместить утверждение после цикла, но не смог заставить его доказать.
protected type P is
procedure Claim (Succeeded : out Boolean);
private
Claimed : Boolean := False;
end P;
subtype Thread_Range is Integer range 1 .. 2;
Ps : array (Thread_Range) of P;
Ts : array (Thread_Range) of T;
task body T is
Which : Integer;
begin
Claim:
for J in Thread_Range loop
declare
Claimed : Boolean;
begin
Ps (J).Claim (Succeeded => Claimed);
if Claimed then
Which := J;
exit Claim;
end if;
end;
end loop Claim;
loop -- having a loop keeps gnatprove quiet
delay until Ada.Real_Time.Time_Last;
end loop;
end T;
protected body P is
procedure Claim (Succeeded : out Boolean) is
begin
if not Claimed then
Claimed := True;
Succeeded := True;
else
Succeeded := False;
end if;
end Claim;
end P;
После внеполосных обсуждений с Джоном мы обнаружили, что это постусловие может быть доказано:
procedure Claim (Succeeded : out Boolean)
with
Post =>
(Is_Claimed'Old or (Succeeded and Is_Claimed))
or
(not Succeeded and Is_Claimed);
Обратите внимание, что это не так, P’Old.Is_Claimed
главным образом потому, что ’Old
требуется копия, и P
она ограничена (поскольку это защищенный тип).
Мы также нашли несколько альтернативных формулировок, которые подтверждаются в GPL 2017, но не в CE 2018: например,
(Is_Claimed
and
(Is_Claimed'Old xor Succeeded)
Комментарии:
1. Как задать постусловие в цикле? У меня есть нечто подобное, и я устанавливаю постусловие на
P.Claim
, и это проходит через SPARK. Но у меня такое чувство, что это не то, что вы имеете в виду.2. Я бы хотел установить постусловие,
P.Claim
чтобы сказать, что еслиP’Old
было заявлено, тоSucceeded
было быFalse
и т.д., НоP’Old
это невозможно для ограниченного типа, и в любом случае вам понадобится функция, чтобы определить, было лиP
заявлено или нет, и она была бы изменчивой, а это незаконно в данном контексте. Я управлял утверждением после цикла (но не смог это доказать, см. Отсутствие навыков выше)3. Это решение, которое я искал, и что более важно, я вижу, что техника № 2, которая мне понравилась, на самом деле была на правильном пути. Ошибки Gnatprove, которые я получал, на самом деле были связаны с чем-то другим. Спасибо!
Ответ №2:
Я не эксперт в этом, но, похоже, вы не можете показать SPARK, что существует однозначная связь между экземпляром задачи и экземпляром защищенного объекта, если вы явно не ссылаетесь на этот экземпляр защищенного объекта из экземпляра задачи. Это, в частности, для того, чтобы заставить SPARK доказать, что только одна задача будет стоять в очереди при входе в защищенный объект; Wait
запись в коде ниже). Следовательно (и хотя это может быть не совсем то, что вы ищете), я мог решить проблему подключения задач и защищенных объектов и в то же время иметь функциональность монитора, используя универсальный пакет, который может создаваться несколько раз. Это подтверждается в GNAT CE 2018:
generic
package Generic_Worker with SPARK_Mode is
task T;
protected P is
entry Wait;
procedure Trigger;
private
Triggered : Boolean := False;
end P;
end Generic_Worker;
с телом:
package body Generic_Worker with SPARK_Mode is
task body T is
begin
loop -- Ravenscar: Tasks must not terminate.
P.Wait;
end loop;
end T;
protected body P is
entry Wait when Triggered is
begin
Triggered := False;
-- Do some work.
end Wait;
procedure Trigger is
begin
Triggered := True;
end Trigger;
end P;
end Generic_Worker;
и создания экземпляров:
with Generic_Worker;
pragma Elaborate_All (Generic_Worker);
package Workers with SPARK_Mode is
package Worker_0 is new Generic_Worker;
package Worker_1 is new Generic_Worker;
package Worker_2 is new Generic_Worker;
package Worker_3 is new Generic_Worker;
package Worker_4 is new Generic_Worker;
end Workers;
Комментарии:
1. Спасибо, что взглянули на это! Однако я думаю, что вы, возможно, неправильно поняли вопрос; я не обязательно хотел доказывать, что конкретная задача связана с определенным защищенным объектом; я просто хотел получить массив защищенных объектов, каждый из которых соответствует одной задаче из равного числа задач, и я хотел сделать это без создания конкретных объектов. Пока я изучаю SPARK, найти хорошую технику было непросто. Ваше решение выглядит как улучшение метода № 1, описанного выше, и хотя мне нравится это улучшение, на самом деле мне не нравится этот метод (например, если
n
является переменной).