Ada 202x (19日目) - Globalその1¶
続き。
これまでのあらすじ¶
Ada 202xでは複数のスレッドが同期なしで同じ変数にアクセスしていないことを検証できます。 具体的には各変数ごとに読み取り(in)と書き込み(out)のアクセスが衝突しているかがチェックされます。
さて、コンパイラが自動でチェックするにも限りがあります。 具体的には翻訳単位や可視性の壁があります。 これらの壁を越えるためにはパッケージの内側で何にアクセスしているかを外に向けて示す必要があります。
グローバル変数アクセスの表明¶
Globalアスペクト(グローバル変数)¶
Global
アスペクトを通じてサブプログラムがアクセスしているグローバル変数を表明することができます。
package Pkg1 is
G : Integer;
function Get_G return Integer
with Global => in G;
procedure Set_G (Value : in Integer)
with Global => out G;
procedure Incr_G
with Global => in out G;
procedure Unrelated
with Global => null;
end Pkg1;
package body Pkg1 is
function Get_G return Integer is
begin
return G; -- in
end Get_G;
procedure Set_G (Value : in Integer) is
begin
G := Value; -- out
end Set_G;
procedure Incr_G is
begin
G := G + 1; -- in out
end Incr_G;
procedure Unrelated is
begin
null;
end Unrelated;
end Pkg1;
割と特殊な文法です。
null
はグローバル変数にアクセスしていないということです。
all
と書きますと全てのグローバル変数にアクセスしていると見做されます。
どのグローバル変数にアクセスしているかがわからない場合が all
ですね。
Global
を指定しないときのデフォルトは、Pure
なパッケージでは Global => null
、それ以外では Global => in out all
となります。
一般にグローバル変数にアクセスしていない関数をリエントラント(再入可能)と呼び他のスレッドのことを気にせず使える性質とされています。
Global
アスペクトでは、リエントラントではないがこれとこれは同時に使っても大丈夫、みたいなことも表現できるわけですね。
それでは G
を外からは見えない場所へ移動してみましょう。
package Pkg1 is
function Get_G return Integer
with Global => in Pkg1;
procedure Set_G (Value : in Integer)
with Global => out Pkg1;
procedure Incr_G
with Global => in out Pkg1;
procedure Unrelated
with Global => null;
private
G : Integer;
end Pkg1;
Global => private of Pkg1
で Pkg1
にある隠されたグローバル変数にアクセスしているという意味です。
Global => Pkg1
で Pkg1
にある全てのグローバル変数にアクセスしているという意味です。
なおこの Global
アスペクトはSPARKでも使用されており全体を通じての整合性が検証されます。
そちらでは仮想のグローバル変数(状態)を宣言できたり状態間の関係を示せたりとより細かく制御できるよう拡張がなされています。
Ada 202xの範囲ではSPARKほど細かいことはできませんので、グローバル変数ではないがアクセスが競合していないかチェックしたいもの、例えばリンクするライブラリの内部状態やOSやCPUの持っている状態等はいずれかのパッケージに代表させればよいと思います。 細かく分けたいならそのための空パッケージを作ったりもありでしょうか。
スレッド間での衝突検証では同期なしにアクセスしている場合と同期を行ってアクセスしている場合を区別する必要があります。 きちんと同期を行っている場合は複数スレッドからアクセスされても問題ありませんが、同期なしにアクセスしているスレッドが混じると危険です。
同期を行っている場合は変数名の前に synchronized
を書きます。
(また予約語を再利用している……。)
package Pkg2 is
S : Integer;
procedure Sync_Incr_S
with Global => in out synchronized;
end Pkg2;
package body Pkg2 is
protected Prot is
procedure Incr_S;
end Prot;
protected body Prot is
procedure Incr_S is
begin
S := S + 1; -- in out
end Incr_S;
end Prot;
procedure Sync_Incr_S is
begin
Prot.Incr_S;
end Sync_Incr_S;
end Pkg2;
グローバル変数が複数ある場合は括弧で囲いaggregate式のように書きます。
package Pkg2 is
X, Y : Integer;
procedure Set_X_As_Y
with Global => (out X, in Y);
end Pkg2;
package body Pkg2 is
procedure Set_X_As_Y is
begin
X := Y;
end Set_X_As_Y;
end Pkg2;
Nonblocking
同様に generic
の引数として渡されたサブプログラムの Global
を参照できます。
generic
with function F (X : Integer) return Integer;
function G return Integer
with
Nonblocking => F'Nonblocking,
Global => F'Global;
function G return Integer is
begin
return F (0);
end G;
複数まとめるときは &
演算子を使います。
(配列を真似ていると思われます。
ただし [
]
は使えません……。)
generic
with function F1 (X : Integer) return Integer;
with function F2 (X : Integer) return Integer;
function G return Integer
with
Nonblocking => F1'Nonblocking and F2'Nonblocking,
Global => F1'Global & F2'Global;
function G return Integer is
begin
return F1 (F2 (0));
end G;
genericの引数との論理積は常に取られるようになりましたので、このようにわざわざ書く必要はなくなりました。
長くなりましたので今日はこの辺にして残りは後日とさせてください。 (例によって小出し。)
関連AI¶
AI12-0302-1 Default Global aspect for language-defined units
AI12-0079-1 Global-in and global-out annotations
AI12-0303-1 Some constants must be covered by Global aspects; extensibility
AI12-0310-1 Specifying private parts of packages in aspect Global
所感¶
Nonblocking
と並んで標準ライブラリの定義のほとんどに with Global
が付きまくってノイズです。