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択になります。
いやー、便利ですね。