AdaのProtected

AdaのProtectedはイカれてる。便利すぎる。

まぁProtectedで出来ることは、Taskが有れば出来るんですけど、スレッドってコスト高いじゃないですか。普通の変数が同期処理してくれたらそっちのほうが便利ですよね。

Protectedの副プログラムはJavaでいうSynchronizedな雰囲気です。先に突入してるタスクが有る場合は、後続の人はキューに並ぶことになります。

with Ada.Text_IO;         use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Protected_Test is
   protected type Is_Synchronized is
      procedure Write_And_Sleep (I : in Integer);
      function  Get_And_Sleep return Integer;
   private
      V : Integer := 0;
   end Is_Synchronized;

   protected body Is_Synchronized is
      procedure Write_And_Sleep (I : in Integer) is
      begin
         Put_Line ("Write: " & Integer'Image(I));
         V := I;
         Put_Line ("Write Zzz...");
         delay 3.0;
         Put_Line ("Write Check: " & Integer'Image(V));
      end Write_And_Sleep;

      function Get_And_Sleep return Integer is
         R : Integer;
      begin         Put_Line ("Get: " & Integer'Image(V));
         R := V;
         Put_Line ("Get Zzz...");
         delay 3.0;
         Put_Line ("Get Check" & Integer'Image(V));
         return R;
      end Get_And_Sleep;
   end Is_Synchronized;

   P : Is_Synchronized;

   task Tester_17 is
      entry Write;
      entry Join;
      entry Write_Again;
   end Tester_17;

   task body Tester_17 is
   begin
      accept Write;
      Put_Line ("Tester 17 is writing!");
      P.Write_And_Sleep (17);
      accept Join;
      accept Write_Again;
      Put_Line ("Tester 17 is writing again!");
      P.Write_And_Sleep (17);
   end Tester_17;

   task Tester_13 is
      entry Write;
      entry Join;
      entry Get;
   end Tester_13;

   task body Tester_13 is
   begin
      accept Write;
      Put_Line ("Tester 13 is writing!");
      P.Write_And_Sleep (13);
      accept Join;
      accept Get;
      Put_Line ("Tester 13 is getting!");
      Put_Line ("Tester 13 received:"  & Integer'Image(P.Get_And_Sleep));
   end Tester_13;

begin
   Tester_17.Write;
   Tester_13.Write;
   Tester_17.Join;
   Tester_13.Join;
   Tester_13.Get;
   Tester_17.Write_Again;
end Protected_Test;
Tester 17 is writing!
Tester 13 is writing!
Write:  17
Write Zzz...
Write Check:  17
Write:  13
Write Zzz...
Write Check:  13
Tester 13 is getting!
Get:  13
Get Zzz...
Tester 17 is writing again!
Get Check 13
Tester 13 received: 13
Write:  17
Write Zzz...
Write Check:  17

最初の書き込みで13は17が終わるまで待っているのが分かります。意図的に3秒間何もしていないのですが、律儀ですね。

次に、13が値の取得に3秒も掛けている間、17は別の副プログラムを呼ぼうとしているにも関わらず、きちんとキューで待っています。副プログラムごとのロックではなくて、オブジェクト全体で1つのロックを持っていることが分かります。なお、functionのロックはread-onlyなので、procedureが走っていない状態ならば複数のfunctionを同時に処理することも出来る事になっています。

Protectedではentryも使えますよ。entryを使うと、ガードのおかげでポーリングというか待機が楽です。

例えば他のTaskが1秒ごとにProtectedの値を1ずつ増やしていくとして、その値が17になるまで待ってから0に戻す処理を書きたい場合。

with Ada.Text_IO;         use Ada.Text_IO;

procedure Protected_Entry is
   protected type Syncronized is
      entry Entry_If_Over_17;
      procedure Procedure_If_Over_17;
      procedure Procedure_If_Over_17 (Result : out Boolean);
      procedure Count_Up;
   private
      V : Integer := 0;
   end Syncronized;

   protected body Syncronized is
      entry Entry_If_Over_17 when V > 17 is
      begin
         V := 0;
      end Entry_If_Over_17;

      procedure Procedure_If_Over_17 is
      begin
         loop -- this cause a dead lock!
            if V > 17 then
               V := 0;
               exit;
            end if;
         end loop;
      end Procedure_If_Over_17;

      procedure Procedure_If_Over_17 (Result : out Boolean) is
      begin
         if V > 17 then
            V := 0;
            Result := True;
         else
            Result := False;
         end if;
      end Procedure_If_Over_17;

      procedure Count_Up is
      begin
         Put_Line ("Now: " & Integer'Image(V));
         V := V + 1;
      end Count_Up;

   end Syncronized;

   P : Syncronized;

   task Count_Up is
   end Count_Up;
   task body Count_Up is
   begin
      loop
         P.Count_Up;
         delay 1.0;
      end loop;
   end Count_Up;

   task Wait_Over_17 is
   end Wait_Over_17;

   task body Wait_Over_17 is
   begin
      -- smart way with entry
      P.Entry_If_Over_17;
      Put_Line ("Entry Sucsess.");

      -- with procedure with polling
      declare
         Result : Boolean;
      begin
         loop
            -- I'm at the end of the queue every time...
            -- and this is a busy loop...
            P.Procedure_If_Over_17 (Result);
              if Result then
                 Put_Line ("Procedure Sucsess?");
              exit;
              end if;
         end loop;
      end;

      -- and then, this will be never returned.
      P.Procedure_If_Over_17;
      Put_Line ("When you see this message...");
   end Wait_Over_17;
begin
   null;
end Protected_Entry;

entryを使わない場合、ビジーループで待つか、デッドロックで死ぬかの2択になります。

いやー、便利ですね。