태스킹
태스킹 (Tasking)
태스크(task)와 보호 객체(protected object)를 사용하면 Ada에서 동시성(concurrency)을 구현할 수 있어요. 다음 절들에서 이 개념들을 자세히 다룰게요.
출처: 태스킹 문서
본문
태스크 (Tasks)
태스크는 메인 애플리케이션과 동시에(concurrently) 실행되는 애플리케이션이라고 생각할 수 있어요. 다른 프로그래밍 언어에서는 태스크를 스레드(thread)라고 부르고, 태스킹을 멀티스레딩(multithreading)이라고 부르기도 해요.
태스크는 메인 애플리케이션과 동기화할 수도 있지만, 메인 애플리케이션과 완전히 독립적으로 정보를 처리할 수도 있어요. 여기서는 이것이 어떻게 이루어지는지 보여줄게요.
간단한 태스크 (Simple task)
태스크는 task 키워드로 선언해요. 태스크 구현은 task body 블록에 지정해요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Simple_Task is
task T;
task body T is
begin
Put_Line ("In task T");
end T;
begin
Put_Line ("In main");
end Show_Simple_Task;
여기서 태스크 T를 선언하고 구현해요. 메인 애플리케이션이 시작되는 즉시 태스크 T도 자동으로 시작돼요 — 태스크를 수동으로 시작할 필요는 없어요. 위 애플리케이션을 실행하면 두 Put_Line 호출이 모두 수행되는 것을 볼 수 있어요. 참고할 점은:
- 메인 애플리케이션 자체가 하나의 태스크(메인 또는 "환경(environment)" 태스크)예요.
- 이 예시에서 서브프로그램
Show_Simple_Task가 애플리케이션의 메인 태스크예요. - 태스크
T는 하위 태스크(subtask)예요. - 각 하위 태스크는 마스터(master)를 가지는데, 이는 하위 태스크가 선언된 프로그램 구조를 나타내요. 이 경우 메인 서브프로그램
Show_Simple_Task가T의 마스터예요. - 마스터 구조는 이를 둘러싼 어떤 태스크가 실행하는데, 이를 하위 태스크의 "마스터 태스크(master task)"라고 부를게요.
태스크 수는 하나로 제한되지 않아요. 위 예시에 태스크 T2를 추가할 수 있어요. 이 태스크도 자동으로 시작되며 태스크 T와 메인 태스크 둘 다와 동시에 실행돼요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Simple_Tasks is
task T;
task T2;
task body T is
begin
Put_Line ("In task T");
end T;
task body T2 is
begin
Put_Line ("In task T2");
end T2;
begin
Put_Line ("In main");
end Show_Simple_Tasks;
간단한 동기화 (Simple synchronization)
방금 봤듯이, 마스터 구조가 "begin"에 도달하는 즉시 그 하위 태스크들도 자동으로 시작돼요. 마스터는 처리할 것이 없어질 때까지 계속 처리해요. 하지만 그 시점에 마스터는 종료되지 않아요. 대신 마스터는 하위 태스크들이 끝날 때까지 기다렸다가 그 후에야 완료될 수 있어요. 즉, 이 대기 과정이 마스터 태스크와 그 하위 태스크 사이의 동기화를 제공해요. 이 동기화 후에 마스터 구조가 완료돼요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Simple_Sync is
task T;
task body T is
begin
for I in 1 .. 10 loop
Put_Line ("hello");
end loop;
end T;
begin
null;
-- 모든 태스크가 종료될 때까지
-- 여기서 대기해요
end Show_Simple_Sync;
같은 메커니즘이 하위 태스크를 포함하는 다른 서브프로그램에도 적용돼요: 서브프로그램 실행은 하위 태스크들이 끝날 때까지 기다려요. 그래서 이 메커니즘은 메인 서브프로그램에만 국한되지 않고, 메인 서브프로그램이 직접 또는 간접적으로 호출하는 모든 서브프로그램에도 적용돼요.
태스크를 별도 패키지로 옮겨도 동기화는 발생해요. 아래 예시에서 패키지 Simple_Sync_Pkg에 태스크 T를 선언해요.
package Simple_Sync_Pkg is
task T;
end Simple_Sync_Pkg;
이에 해당하는 패키지 본문은 다음과 같아요:
with Ada.Text_IO; use Ada.Text_IO;
package body Simple_Sync_Pkg is
task body T is
begin
for I in 1 .. 10 loop
Put_Line ("hello");
end loop;
end T;
end Simple_Sync_Pkg;
메인 프로시저가 패키지를 with하므로, 패키지에 정의된 태스크 T는 메인 태스크의 하위 태스크가 돼요. 예를 들어:
with Simple_Sync_Pkg;
procedure Test_Simple_Sync_Pkg is
begin
null;
-- 모든 태스크가 종료될 때까지
-- 여기서 대기해요
end Test_Simple_Sync_Pkg;
메인 서브프로그램이 반환되는 즉시 메인 태스크는 Simple_Sync_Pkg의 태스크 T가 생성한 하위 태스크들과 동기화한 후 마침내 종료돼요.
지연 (Delay)
delay 키워드를 사용해 지연을 도입할 수 있어요. 이는 현재 태스크를 delay 문에 지정된 시간(초 단위)만큼 재우는 것(sleep)이에요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Delay is
task T;
task body T is
begin
for I in 1 .. 5 loop
Put_Line ("hello from task T");
delay 1.0;
-- ^ 1.0초 대기
end loop;
end T;
begin
delay 1.5;
Put_Line ("hello from main");
end Show_Delay;
이 예시에서는 태스크 T가 "hello" 메시지를 표시할 때마다 1초씩 기다리게 해요. 또한 메인 태스크는 자신의 "hello" 메시지를 표시하기 전에 1.5초를 기다려요.
동기화: 랑데부 (Synchronization: rendezvous)
지금까지 본 유일한 동기화는 마스터 구조가 하위 태스크와 함께 끝날 때 자동으로 발생하는 것이었어요. entry 키워드를 사용해 사용자 정의 동기화 지점을 만들 수도 있어요. 엔트리(entry) 는 특별한 종류의 서브프로그램으로 볼 수 있는데, 다른 태스크가 비슷한 구문으로 호출해요(이후에 보게 될 거예요).
태스크 본문 정의에서 accept 키워드를 사용해 태스크의 어느 부분이 엔트리를 받아들일지 정의해요. 태스크는 accept 문에 도달할 때까지 진행한 다음 다른 태스크가 자신과 동기화하기를 기다려요. 구체적으로:
- 엔트리를 가진 태스크는 그 지점(
accept문)에서 기다리며, 마스터 태스크로부터 해당 엔트리에 대한 호출을 받을 준비를 해요. - 다른 태스크는 프로시저 호출과 유사한 방식으로 태스크 엔트리를 호출해 엔트리와 동기화해요.
태스크 간 이러한 동기화를 랑데부(rendezvous) 라고 불러요. 예시를 볼게요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Rendezvous is
task T is
entry Start;
end T;
task body T is
begin
accept Start;
-- ^ 누군가 엔트리를
-- 호출하기를 기다림
Put_Line ("In T");
end T;
begin
Put_Line ("In Main");
-- T의 엔트리 호출:
T.Start;
end Show_Rendezvous;
이 예시에서 태스크 T에 엔트리 Start를 선언해요. 태스크 본문에서 accept Start를 사용해 이 엔트리를 구현해요. 태스크 T가 이 지점에 도달하면 다른 태스크가 자신의 엔트리를 호출하기를 기다려요. 이 동기화는 T.Start 문에서 발생해요. 랑데부가 완료된 후에는 메인 태스크와 태스크 T가 메인 서브프로그램 Show_Rendezvous가 끝날 때 마지막으로 한 번 동기화할 때까지 다시 동시에 실행돼요.
엔트리는 단순한 태스크 동기화 이상의 용도로 사용될 수 있어요: 두 태스크가 동기화되는 동안 여러 문장을 수행할 수도 있어요. 이는 do ... end 블록으로 해요. 앞선 예시에서는 간단히 accept Start do <statements>; end;라고 쓰면 돼요. 이 종류의 블록은 다음 예시에서 사용할게요.
셀렉트 루프 (Select loop)
엔트리를 받아들일 수 있는 횟수에는 제한이 없어요. 태스크에 무한 루프를 만들고 같은 엔트리에 대한 호출을 계속 받아들일 수도 있어요. 하지만 무한 루프는 하위 태스크가 끝나지 못하게 하므로, 마스터가 처리 끝에 도달했을 때 마스터 태스크를 막아요. 따라서 태스크 본문에서 accept 문을 포함한 루프는 select ... or terminate 문과 함께 사용할 수 있어요. 간단히 말해, 이 문은 마스터 구조가 끝에 도달했을 때 마스터 태스크가 하위 태스크를 자동으로 종료시킬 수 있게 해줘요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Rendezvous_Loop is
task T is
entry Reset;
entry Increment;
end T;
task body T is
Cnt : Integer := 0;
begin
loop
select
accept Reset do
Cnt := 0;
end Reset;
Put_Line ("Reset");
or
accept Increment do
Cnt := Cnt + 1;
end Increment;
Put_Line ("In T's loop ("
& Integer'Image (Cnt)
& ")");
or
terminate;
end select;
end loop;
end T;
begin
Put_Line ("In Main");
for I in 1 .. 4 loop
-- T의 엔트리를 여러 번 호출
T.Increment;
end loop;
T.Reset;
for I in 1 .. 4 loop
-- T의 엔트리를 여러 번 호출
T.Increment;
end loop;
end Show_Rendezvous_Loop;
이 예시에서 태스크 본문은 Reset과 Increment 엔트리에 대한 호출을 받아들이는 무한 루프를 구현해요. 다음을 관찰할 수 있어요:
accept E do ... end블록은 카운터를 증가시키는 데 사용돼요.- 태스크
T가do ... end블록을 수행하는 동안 메인 태스크는 블록 완료를 기다려요. - 메인 태스크는
1 .. 4루프에서Increment엔트리를 여러 번 호출해요. 또한 두 번째 루프 전에Reset엔트리도 호출해요. - 태스크
T가 무한 루프를 포함하므로 항상Reset과Increment엔트리에 대한 호출을 받아들여요. - 하위 태스크의 마스터 구조(
Show_Rendezvous_Loop서브프로그램)가 완료되면T태스크의 상태를 확인해요. 태스크T가Reset또는Increment엔트리에 대한 새 호출을 받아들일 수 있음에도 불구하고,select문의or terminate부분 덕분에 마스터 구조는 태스크T를 종료시킬 수 있어요.
순환 태스크 (Cycling tasks)
이전 예시에서 delay 키워드로 태스크를 지정된 시간만큼 지연시키는 방법을 봤어요. 하지만 루프에서 delay 문을 사용하는 것만으로는 delay 문 사이의 규칙적인 간격을 보장하기에 충분하지 않아요. 예를 들어, 연속된 delay 문 사이에 계산 집약적인 프로시저 호출이 있을 수 있어요:
while True loop
delay 1.0;
-- ^ 1.0초 대기
Computational_Intensive_App;
end loop;
이 경우 Computational_Intensive_App 프로시저가 도입하는 시간 드리프트(time drift) 때문에 delay 문을 10번 호출한 후 정확히 10초가 경과했는지 보장할 수 없어요. 많은 경우 이 시간 드리프트는 중요하지 않으므로 delay 키워드를 사용하는 것으로 충분해요.
하지만 시간 드리프트가 허용되지 않는 상황도 있어요. 그런 경우 delay until 문을 사용해야 해요. 이 문은 지연이 끝나는 정확한 시간을 받아 규칙적인 간격을 정의할 수 있게 해줘요. 이것은 예를 들어 실시간(real-time) 애플리케이션에서 유용해요.
곧 이 시간 드리프트가 어떻게 도입되는지, 그리고 delay until 문이 어떻게 문제를 우회하는지 예시를 보여줄게요. 그 전에 먼저 경과 시간을 측정하는 프로시저(Show_Elapsed_Time)와 간단한 delay로 시뮬레이션되는 더미 Computational_Intensive_App 프로시저를 포함하는 패키지를 살펴볼게요. 이게 완전한 패키지예요:
with Ada.Real_Time; use Ada.Real_Time;
package Delay_Aux_Pkg is
function Get_Start_Time return Time
with Inline;
procedure Show_Elapsed_Time
with Inline;
procedure Computational_Intensive_App;
private
Start_Time : Time := Clock;
function Get_Start_Time return Time is
(Start_Time);
end Delay_Aux_Pkg;
with Ada.Text_IO; use Ada.Text_IO;
package body Delay_Aux_Pkg is
procedure Show_Elapsed_Time is
Now_Time : Time;
Elapsed_Time : Time_Span;
begin
Now_Time := Clock;
Elapsed_Time := Now_Time - Start_Time;
Put_Line ("Elapsed time "
& Duration'Image
(To_Duration (Elapsed_Time))
& " seconds");
end Show_Elapsed_Time;
procedure Computational_Intensive_App is
begin
delay 0.5;
end Computational_Intensive_App;
end Delay_Aux_Pkg;
이 보조 패키지를 사용해 시간 드리프트가 발생하는 애플리케이션을 작성할 준비가 됐어요:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Real_Time; use Ada.Real_Time;
with Delay_Aux_Pkg;
procedure Show_Time_Task is
package Aux renames Delay_Aux_Pkg;
task T;
task body T is
Cnt : Integer := 1;
begin
for I in 1 .. 5 loop
delay 1.0;
Aux.Show_Elapsed_Time;
Aux.Computational_Intensive_App;
Put_Line ("Cycle # "
& Integer'Image (Cnt));
Cnt := Cnt + 1;
end loop;
Put_Line ("Finished time-drifting loop");
end T;
begin
null;
end Show_Time_Task;
애플리케이션을 실행해 보면, Computational_Intensive_App이 도입한 드리프트 때문에 루프 3회 반복 후 이미 약 4초의 시간 차이가 생기는 것을 볼 수 있어요. 하지만 delay until 문을 사용하면 이 시간 드리프트를 피하고 정확히 1초의 규칙적인 간격을 가질 수 있어요:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Real_Time; use Ada.Real_Time;
with Delay_Aux_Pkg;
procedure Show_Time_Task is
package Aux renames Delay_Aux_Pkg;
task T;
task body T is
Cycle : constant Time_Span :=
Milliseconds (1000);
Next : Time := Aux.Get_Start_Time
+ Cycle;
Cnt : Integer := 1;
begin
for I in 1 .. 5 loop
delay until Next;
Aux.Show_Elapsed_Time;
Aux.Computational_Intensive_App;
-- 1초 주기를 사용해
-- 다음 실행 시간 계산
Next := Next + Cycle;
Put_Line ("Cycle # "
& Integer'Image (Cnt));
Cnt := Cnt + 1;
end loop;
Put_Line ("Finished cycling");
end T;
begin
null;
end Show_Time_Task;
이제 애플리케이션을 실행해 보면 delay until 문이 Computational_Intensive_App이 반복 사이의 1초 규칙 간격을 방해하지 않도록 보장하는 것을 알 수 있어요.
보호 객체 (Protected objects)
여러 태스크가 공유 데이터에 접근할 때 데이터 손상이 발생할 수 있어요. 예를 들어, 한 태스크가 다른 태스크가 동시에 읽고 있는 정보의 일부를 덮어쓰면 데이터가 일관되지 않을 수 있어요. 이런 종류의 문제를 피하고 정보에 질서 있게 접근하도록 보장하기 위해 보호 객체(protected objects) 를 사용해요.
보호 객체는 데이터를 캡슐화하고 보호 연산(protected operations) 을 통해 그 데이터에 대한 접근을 제공해요. 보호 연산은 서브프로그램이나 보호 엔트리일 수 있어요. 보호 객체를 사용하면 경쟁 조건(race condition)이나 다른 동시 접근으로 인해 데이터가 손상되지 않도록 보장할 수 있어요.
중요 (Important) 객체는 Ada 태스크를 사용해서도 동시 접근으로부터 보호할 수 있어요. 사실 이것이 Ada 83(Ada 언어의 첫 버전)에서 객체를 동시 접근으로부터 보호하는 유일한 방법이었어요. 하지만 보호 객체를 사용하는 것이 태스크만으로 유사한 메커니즘을 구현하는 것보다 훨씬 단순해요. 따라서 주 목적이 데이터를 보호하는 것이라면 보호 객체를 사용해야 해요.
간단한 객체 (Simple object)
보호 객체는 protected 키워드로 선언해요. 구문은 패키지에 사용되는 것과 유사해요: 공개 부분에는 연산(예: 프로시저와 함수)을, 비공개 부분에는 데이터를 선언할 수 있어요. 연산의 해당 구현은 객체의 protected body에 포함돼요. 예를 들어:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Protected_Objects is
protected Obj is
-- 연산은 여기에 (서브프로그램만)
procedure Set (V : Integer);
function Get return Integer;
private
-- 데이터는 여기에
Local : Integer := 0;
end Obj;
protected body Obj is
-- 프로시저는 데이터를 수정할 수 있음
procedure Set (V : Integer) is
begin
Local := V;
end Set;
-- 함수는 데이터를 수정할 수 없음
function Get return Integer is
begin
return Local;
end Get;
end Obj;
begin
Obj.Set (5);
Put_Line ("Number is: "
& Integer'Image (Obj.Get));
end Show_Protected_Objects;
이 예시에서 Obj에 대해 두 연산 Set과 Get을 정의해요. 이 연산들의 구현은 Obj 본문에 있어요. 이 연산들을 작성하는 구문은 일반 프로시저와 함수와 동일해요. 보호 객체의 구현은 직관적이에요 — 이 서브프로그램들에서 Local에 접근하고 갱신할 뿐이에요. 메인 애플리케이션에서 이 연산들을 호출하려면 접두 표기법(예: Obj.Get)을 사용해요.
엔트리 (Entries)
보호 프로시저와 함수 외에도 보호 엔트리 포인트(protected entry point)를 정의할 수 있어요. 이는 entry 키워드로 해요. 보호 엔트리 포인트를 사용하면 when 키워드로 배리어(barrier)를 정의할 수 있어요. 배리어는 엔트리가 실제 처리를 시작하기 전에 충족되어야 하는 조건이에요 — 조건이 충족되면 배리어가 해제(releasing) 된다고 말해요.
앞선 예시는 보호 객체에 대한 연산을 정의하는 데 프로시저와 함수를 사용했어요. 하지만 그렇게 하면 설정(Set)되기 전에 보호 정보를 Obj.Get을 통해 읽는 것이 허용돼요. 그것을 정의된 연산으로 허용하기 위해 기본값(0)을 지정했었어요. 대신 Obj.Get을 함수 대신 엔트리 로 다시 작성하면 배리어를 구현해, 정보가 설정되기 전에는 어떤 태스크도 그 정보를 읽을 수 없게 보장할 수 있어요.
다음 예시는 Obj.Get 연산에 대한 배리어를 구현해요. 또한 보호 객체에 접근하려 시도하는 두 개의 동시 서브프로그램(메인 태스크와 태스크 T)을 포함해요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Protected_Objects_Entries is
protected Obj is
procedure Set (V : Integer);
entry Get (V : out Integer);
private
Local : Integer;
Is_Set : Boolean := False;
end Obj;
protected body Obj is
procedure Set (V : Integer) is
begin
Local := V;
Is_Set := True;
end Set;
entry Get (V : out Integer)
when Is_Set is
-- 조건이 참이 될 때까지
-- 엔트리는 차단됨. 배리어는
-- 엔트리 호출 시와 프로시저 및
-- 엔트리의 종료 시에 평가됨.
-- 호출하는 태스크는 배리어가
-- 해제될 때까지 잠을 잠.
begin
V := Local;
Is_Set := False;
end Get;
end Obj;
N : Integer := 0;
task T;
task body T is
begin
Put_Line
("Task T will delay for 4 seconds...");
delay 4.0;
Put_Line
("Task T will set Obj...");
Obj.Set (5);
Put_Line
("Task T has just set Obj...");
end T;
begin
Put_Line
("Main application will get Obj...");
Obj.Get (N);
Put_Line
("Main application has retrieved Obj...");
Put_Line
("Number is: " & Integer'Image (N));
end Show_Protected_Objects_Entries;
실행해 보면 메인 애플리케이션은 정보를 읽기(Obj.Get을 통해) 전에 태스크 T의 Obj.Set 호출로 보호 객체가 설정될 때까지 기다려요. 태스크 T에 4초 지연이 추가되었으므로 메인 애플리케이션도 4초 지연돼요. 이 지연 후에만 태스크 T가 객체를 설정하고 Obj.Get의 배리어를 해제해, 메인 애플리케이션이 (보호 객체에서 정보를 검색한 후) 처리를 재개할 수 있게 돼요.
태스크 타입과 보호 타입 (Task and protected types)
앞선 예시들에서는 단일 태스크와 보호 객체를 정의했어요. 하지만 태스크와 보호 객체를 타입 정의로 일반화할 수 있어요. 이를 통해 예를 들어 단일 태스크 타입만으로 여러 태스크를 만들 수 있게 돼요.
태스크 타입 (Task types)
태스크 타입은 태스크의 일반화예요. 선언은 단순 태스크와 유사해요: task를 task type으로 바꾸면 돼요. 단순 태스크와 태스크 타입의 차이는 태스크 타입은 자동으로 시작되는 실제 태스크를 만들지 않는다는 점이에요. 대신 태스크 객체 선언이 필요해요. 이는 일반 변수와 타입이 작동하는 방식과 정확히 같아요: 객체는 타입 정의가 아니라 변수 정의로만 만들어져요.
이를 설명하기 위해 첫 예시를 반복할게요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Simple_Task is
task T;
task body T is
begin
Put_Line ("In task T");
end T;
begin
Put_Line ("In main");
end Show_Simple_Task;
이제 task T를 task type TT으로 바꿔 다시 작성해요. 정의 후 태스크 타입 TT에 기반한 태스크(A_Task)를 선언해요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Simple_Task_Type is
task type TT;
task body TT is
begin
Put_Line ("In task type TT");
end TT;
A_Task : TT;
begin
Put_Line ("In main");
end Show_Simple_Task_Type;
이 예시를 확장해 태스크 배열을 만들 수 있어요. 변수 선언과 같은 구문을 사용하므로 태스크 타입에도 유사한 구문을 사용해요: array (<>) of Task_Type. 또한 Start 엔트리를 정의해 개별 태스크에 정보를 전달할 수 있어요. 갱신된 예시는 다음과 같아요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Task_Type_Array is
task type TT is
entry Start (N : Integer);
end TT;
task body TT is
Task_N : Integer;
begin
accept Start (N : Integer) do
Task_N := N;
end Start;
Put_Line ("In task T: "
& Integer'Image (Task_N));
end TT;
My_Tasks : array (1 .. 5) of TT;
begin
Put_Line ("In main");
for I in My_Tasks'Range loop
My_Tasks (I).Start (I);
end loop;
end Show_Task_Type_Array;
이 예시에서 배열 My_Tasks에 5개의 태스크를 선언해요. 엔트리 포인트(Start)에서 개별 태스크에 배열 인덱스를 전달해요. 개별 하위 태스크와 메인 태스크 사이의 동기화 후 각 하위 태스크가 동시에 Put_Line을 호출해요.
보호 타입 (Protected types)
보호 타입은 보호 객체의 일반화예요. 선언은 보호 객체와 유사해요: protected를 protected type으로 바꾸면 돼요. 태스크 타입과 마찬가지로, 보호 타입도 실제 객체를 만들려면 객체 선언이 필요해요. 다시 말하지만 이는 변수 선언과 유사하며 보호 객체의 배열(또는 다른 복합 객체)을 만들 수 있게 해줘요.
이전 예시를 재사용해 보호 타입을 사용하도록 다시 작성할 수 있어요:
with Ada.Text_IO; use Ada.Text_IO;
procedure Show_Protected_Object_Type is
protected type P_Obj_Type is
procedure Set (V : Integer);
function Get return Integer;
private
Local : Integer := 0;
end P_Obj_Type;
protected body P_Obj_Type is
procedure Set (V : Integer) is
begin
Local := V;
end Set;
function Get return Integer is
begin
return Local;
end Get;
end P_Obj_Type;
Obj : P_Obj_Type;
begin
Obj.Set (5);
Put_Line ("Number is: "
& Integer'Image (Obj.Get));
end Show_Protected_Object_Type;
이 예시에서 보호 객체 Obj를 직접 정의하는 대신, 먼저 보호 타입 P_Obj_Type을 정의한 다음 Obj를 그 보호 타입의 객체로 선언해요. 메인 애플리케이션은 변하지 않았어요: 여전히 Obj.Set과 Obj.Get으로 보호 객체에 접근하며, 원래 예시와 동일해요.