Ada: массив задач

#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 более требователен. Я попробовал два подхода, которые, похоже, работают нормально, когда я их запускаю:

  1. Укажите T Integer дискриминант, затем создайте экземпляр T(i); для каждого i , но это становится обременительным при не очень больших i размерах.
 task type T(which: Integer);
t1: T(1);
t2: T(2);
...
  
  1. Добавьте 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 является переменной).