在障碍条件中使用受保护的条目参数

Using protected entry argument in barrier condition

我有一个受保护的 Hashed_Map,带有数据向量。要从特定 Vector 获取元素,我需要将其键传递给条目,如果 Vector 为空,则等待新元素出现在其中。在障碍条件下,关键参数尚不可用,我不得不在采用关键的过程中嵌套一个条目。在这种情况下,会出现有关可能的阻塞操作的警告。

还有其他方法吗?

with Ada.Containers.Vectors;
with Ada.Containers.Hashed_Maps;

package Protected_Map is

   use Ada.Containers;

   type Element_Key is new Positive;
   type Data_Type is null record;

   package Data_Vectors is new Vectors
     (Index_Type   => Natural,
      Element_Type => Data_Type);

   function Data_Vector_Hash
     (Key : Element_Key) return Ada.Containers.Hash_Type is
     (Hash_Type (Key));

   package Data_Vector_Maps is new Hashed_Maps
     (Key_Type        => Element_Key,
      Element_Type    => Data_Vectors.Vector,
      Hash            => Data_Vector_Hash,
      Equivalent_Keys => "=",
      "="             => Data_Vectors."=");

   protected Map is

      procedure Create (Key : out Element_Key);

      procedure Put (Data : Data_Type);

      procedure Get
        (Key  : Element_Key;
         Data : out Data_Type);

      procedure Delete (Key : Element_Key);

   private

      entry Get_Element
        (Key  : Element_Key;
         Data : out Data_Type);

      Data_Vector_Map : Data_Vector_Maps.Map;

   end Map;

end Protected_Map;

由于您的 Element_Key 是离散类型,您可以使用 条目族 (条目数组)。这里也不需要使用实际的地图,一个数组就足够了。

为了使用条目族,您需要限制 Element_Key 的范围以适合您的实际问题(至少有一个流行的编译器将条目族实现为实际数组,因此您会很快 运行如果范围很大则内存不足)。

因此:

package Protected_Map is

   use Ada.Containers;

   type Element_Key is new Positive range 1..10; -- constrained range
   type Data_Type is null record;

   package Data_Vectors is new Vectors
     (Index_Type   => Natural,
      Element_Type => Data_Type);

   type Data_Vector_Array is array(Element_Key) of Data_Vectors.Vector;


   protected Map is

      procedure Put (Key : Element_Key; Data : Data_Type);

      entry Get
        (Element_Key) -- entry family
        (Data : out Data_Type);

      procedure Delete (Key : Element_Key);

   private

      Data_Vector_Map : Data_Vector_Array;

   end Map;

end Protected_Map;

和条目正文:

      entry Get
        (for Key in Element_Key) -- entry family
        (Data : out Data_Type)
        when not Data_Vector_Map(Key).Is_Empty
      is
      begin
         ...
      end Get;

然后(例如)

   for Key in Element_Key'Range loop
      Map.Get(Key)(The_Data);
   end loop;

如果你例子中的map key确实是有限范围内的某个离散值,那么@egilhh的回答确实值得考虑。如果不是这种情况,那么您可以通过使用 Get 条目和一些额外的私有 Get_Retry 条目来解决问题,如下例所示。

当您想检查某个项目(Get 条目)是否可用时使用此“模式”,如果不可用,则将请求重新排队到另一个条目(Get_Retry)会等到新物品到达。该模式通常用于编程线程安全的资源管理器。

在此模式中,Get 条目始终处于启用状态(即守卫从不阻止),因此始终允许请求进入并查看感兴趣的项目是否已经可用:

entry Get (Key : Map_Key; Data : out Data_Type)
  when True   --  Never blocking guard.
is
begin
   if Data_Available (Key) then
      Data := Data_Vector_Map (Key).Last_Element;
      Data_Vector_Map (Key).Delete_Last;
   else
      requeue Get_Retry;   -- No data available, try again later.
   end if;
end Get;

如果没有项目可用,则请求将重新排队到 Get_Retry 条目。这个(私有)入口有一个守卫,它被 Put 子程序解锁。如果一个项目通过 Put 到达,那么 Put 将记录等待重试的请求数,解锁守卫,并允许挂起的请求以查看他们是否对新项目感兴趣。

procedure Put (Key : Map_Key; Data : Data_Type) is
begin
   Data_Vector_Map (Key).Append (Data);
   
   --  If there are requests for data, then record the number
   --  of requests that are waiting and open the guard of Get_Retry.
   if Get_Retry'Count /= 0 then
      Get_Retry_Requests_Left := Get_Retry'Count;
      Get_Retry_Enabled       := True;
   end if;
   
end Put;

一旦所有待处理的请求都被处理一次,Get_Retry 将禁用自身以防止再次向其重新排队的任何请求被再次处理。

entry Get_Retry (Key : Map_Key; Data : out Data_Type)       
  when Get_Retry_Enabled   --  Guard unblocked by Put.
is
begin
   
   --  Set guard once all pending requests have been served once.         
   Get_Retry_Requests_Left := Get_Retry_Requests_Left - 1;
   if Get_Retry_Requests_Left = 0 then
      Get_Retry_Enabled := False;
   end if;
   
   --  Check if data is available, same logic as in Get.
   if Data_Available (Key) then
      Data := Data_Vector_Map (Key).Last_Element;
      Data_Vector_Map (Key).Delete_Last;
   else
      requeue Get_Retry;   -- No data available, try again later.
   end if;
   
end Get_Retry;

注意:两个条目系列(如@egilhh 的回答中所讨论)以及此模式在最近的 AdaCore blogpost.[=28 中进行了讨论=]

protected_map.ads

with Ada.Containers.Vectors;
with Ada.Containers.Hashed_Maps;

package Protected_Map is

   use Ada.Containers;

   type Map_Key is new Positive;
   type Data_Type is new Integer;

   function Data_Vector_Hash (Key : Map_Key) return Hash_Type is
     (Hash_Type (Key));
   
   package Data_Vectors is new Vectors
     (Index_Type   => Natural,
      Element_Type => Data_Type);

   package Data_Vector_Maps is new Hashed_Maps
     (Key_Type        => Map_Key,
      Element_Type    => Data_Vectors.Vector,
      Hash            => Data_Vector_Hash,
      Equivalent_Keys => "=",
      "="             => Data_Vectors."=");

   protected Map is
      procedure Create (Key : Map_Key);
      procedure Delete (Key : Map_Key);
      
      procedure Put (Key : Map_Key; Data : Data_Type);      
      entry Get (Key : Map_Key; Data : out Data_Type);

   private
      
      entry Get_Retry (Key : Map_Key; Data : out Data_Type);
      
      Get_Retry_Requests_Left : Natural := 0;
      Get_Retry_Enabled       : Boolean := False;  
      
      Data_Vector_Map : Data_Vector_Maps.Map;
      
   end Map;

end Protected_Map;

protected_map.adb

package body Protected_Map is

   protected body Map is

      ------------
      -- Create --
      ------------
      
      procedure Create (Key : Map_Key) is
      begin
         Data_Vector_Map.Insert (Key, Data_Vectors.Empty_Vector);
      end Create;

      ------------
      -- Delete --
      ------------
      
      procedure Delete (Key : Map_Key) is
      begin
         Data_Vector_Map.Delete (Key);
      end Delete;
      
      ---------
      -- Put --
      ---------
      
      procedure Put (Key : Map_Key; Data : Data_Type) is
      begin
         Data_Vector_Map (Key).Append (Data);
         
         --  If there are requests for data, then record the number
         --  of requests that are waiting and unblock the guard of Get_Retry.
         if Get_Retry'Count /= 0 then
            Get_Retry_Requests_Left := Get_Retry'Count;
            Get_Retry_Enabled       := True;
         end if;
         
      end Put;
      
      --------------------
      -- Data_Available --
      --------------------
      
      function Data_Available (Key : Map_Key) return Boolean is
      begin
         return Data_Vector_Map.Contains (Key) and then
           not Data_Vector_Map (Key).Is_Empty;
      end Data_Available;
      
      ---------
      -- Get --
      ---------
      
      entry Get (Key : Map_Key; Data : out Data_Type)
        when True   --  No condition.
      is
      begin
         if Data_Available (Key) then
            Data := Data_Vector_Map (Key).Last_Element;
            Data_Vector_Map (Key).Delete_Last;
         else
            requeue Get_Retry;   -- No data available, try again later.
         end if;
      end Get;
      
      ---------------
      -- Get_Retry --
      ---------------
      
      entry Get_Retry (Key : Map_Key; Data : out Data_Type)       
        when Get_Retry_Enabled   --  Guard unblocked by Put.
      is
      begin
         
         --  Set guard once all pending requests have been served once.         
         Get_Retry_Requests_Left := Get_Retry_Requests_Left - 1;
         if Get_Retry_Requests_Left = 0 then
            Get_Retry_Enabled := False;
         end if;
         
         --  Check if data is available, same logic as in Get.
         if Data_Available (Key) then
            Data := Data_Vector_Map (Key).Last_Element;
            Data_Vector_Map (Key).Delete_Last;
         else
            requeue Get_Retry;   -- No data available, try again later.
         end if;
         
      end Get_Retry;
      
   end Map;

end Protected_Map;

main.adb

with Ada.Text_IO; use Ada.Text_IO;
with Protected_Map;

procedure Main is

   task Getter;
   
   task body Getter is
      Data : Protected_Map.Data_Type;
   begin
      Protected_Map.Map.Get (2, Data);
      Put_Line (Data'Image);
      
      Protected_Map.Map.Get (1, Data);
      Put_Line (Data'Image);
      
      Protected_Map.Map.Get (3, Data);
      Put_Line (Data'Image);
      
      Protected_Map.Map.Get (1, Data);
      Put_Line (Data'Image);
      
   end;
   
begin   
   Protected_Map.Map.Create (1);
   Protected_Map.Map.Create (2);
   Protected_Map.Map.Create (3);
   
   Protected_Map.Map.Put (1, 10);
   delay 0.5;   
   Protected_Map.Map.Put (1, 15);
   delay 0.5;   
   Protected_Map.Map.Put (2, 20);
   delay 0.5;   
   Protected_Map.Map.Put (3, 30);
   
end Main;

输出

$ ./obj/main
 20
 15
 30
 10