Ada: 태스킹 (Tasking)

태스크(Tasks)와 보호 객체(protected objects)는 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;Code language: PHP (php)

여기서는 태스크 T를 선언하고 구현하고 있습니다. 메인 애플리케이션이 시작되자마자 태스크 T는 자동으로 시작됩니다 — 이 태스크를 수동으로 시작할 필요는 없습니다. 위 애플리케이션을 실행하면 두 Put_Line 호출이 모두 수행된다는 것을 알 수 있습니다.

참고:

  • 메인 애플리케이션은 그 자체로 태스크입니다 (메인 또는 “환경” 태스크).
    • 이 예제에서는 서브프로그램 Show_Simple_Task가 애플리케이션의 메인 태스크입니다.
  • 태스크 T는 서브태스크(subtask)입니다.
    • 각 서브태스크는 마스터(master)를 가집니다. 마스터는 서브태스크가 선언된 프로그램 구조(construct)를 나타냅니다. 이 경우, 메인 서브프로그램 Show_Simple_TaskT의 마스터입니다.
    • 마스터 구조는 이를 감싸는 어떤 태스크에 의해 실행되며, 우리는 이를 서브태스크의 “마스터 태스크”라고 부를 것입니다.
  • 태스크의 수는 하나로 제한되지 않습니다: 위 예제에 태스크 T2를 포함할 수 있습니다.
    • 이 태스크도 자동으로 시작되며 태스크 T 및 메인 태스크 모두와 동시에(concurrently) 실행됩니다. 예를 들어:
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;Code language: Ada (ada)

간단한 동기화

앞서 보았듯이, 마스터 구조(master construct)가 “begin”에 도달하자마자 그것의 서브태스크(subtasks)들도 자동으로 시작됩니다. 마스터는 더 이상 할 일이 없을 때까지 자신의 처리를 계속합니다. 하지만 그 시점에서 그것은 종료되지 않습니다. 대신 마스터는 자신의 서브태스크들이 완료될 때까지 기다리며, 그 이후에야 자신도 완료됩니다. 다시 말해, 이 대기 과정이 마스터 태스크와 그 서브태스크들 사이에 동기화(synchronization)를 제공합니다. 이 동기화 이후에 마스터 구조가 완료됩니다. 예를 들어:

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;
   -- 여기서 모든 태스크(이 경우 T)가
   -- 종료될 때까지 기다립니다
end Show_Simple_Sync;Code language: Ada (ada)

서브태스크를 포함하는 다른 서브프로그램에서도 동일한 메커니즘이 사용됩니다. 즉, 서브프로그램 실행은 자신의 서브태스크들이 완료될 때까지 기다립니다. 따라서 이 메커니즘은 메인 서브프로그램에 국한되지 않으며, 메인 서브프로그램에 의해 직접적으로든 간접적으로든 호출되는 모든 서브프로그램에도 적용됩니다.

태스크를 별도의 패키지로 옮기더라도 동기화는 또한 발생합니다. 아래 예제에서는 Simple_Sync_Pkg 패키지 안에 태스크 T를 선언합니다.

package Simple_Sync_Pkg is
   task T;
end Simple_Sync_Pkg;Code language: Ada (ada)

다음은 해당 패키지 본문(body)입니다:

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;Code language: Ada (ada)

패키지가 메인 프로시저에 의해 with되었으므로, 그 패키지에 정의된 태스크 T는 메인 태스크의 서브태스크가 됩니다. 예를 들어:

with Simple_Sync_Pkg;

procedure Test_Simple_Sync_Pkg is
begin
   null;
   -- 여기서 모든 태스크 (이 경우 Simple_Sync_Pkg의 T)가
   -- 종료될 때까지 기다립니다
end Test_Simple_Sync_Pkg;Code language: Ada (ada)

메인 서브프로그램이 반환(return)하자마자, 메인 태스크는 최종적으로 종료되기 전에 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;Code language: Ada (ada)

이 예제에서는 태스크 T가 “hello” 메시지를 표시한 후 매번 1초씩 기다리도록 만들고 있습니다. 또한, 메인 태스크는 자신의 “hello” 메시지를 표시하기 전에 1.5초 동안 기다리고 있습니다.

동기화: 랑데부

지금까지 우리가 본 유일한 동기화 유형은 서브태스크를 가진 마스터 구조(master construct)의 끝에서 자동으로 발생하는 것입니다. entry 키워드를 사용하여 사용자 정의 동기화 지점을 정의할 수도 있습니다. entry는 나중에 보게 될 것처럼 유사한 구문을 사용하여 다른 태스크에 의해 호출되는 특별한 종류의 서브프로그램으로 간주될 수 있습니다.

태스크 본문 정의에서는 accept 키워드를 사용하여 태스크의 어느 부분이 엔트리(entries)를 수락(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;
      --       ^ 누군가가 (Waiting for somebody)
      --         엔트리를 호출하기를 기다리는 중 (to call the entry)

      Put_Line ("In T");
   end T;

begin
   Put_Line ("In Main");

   -- T의 엔트리 호출: (Calling T's entry:)
   T.Start;
end Show_Rendezvous;Code language: Ada (ada)

이 예제에서는 태스크 T에 대해 Start 엔트리를 선언합니다. 태스크 본문에서는 accept Start를 사용하여 이 엔트리를 구현합니다. 태스크 T가 이 지점에 도달하면, 다른 태스크가 자신의 엔트리를 호출하기를 기다립니다. 이 동기화는 T.Start 문에서 발생합니다. 랑데부가 완료된 후, 메인 태스크와 태스크 T는 메인 서브프로그램 Show_Rendezvous가 종료될 때 마지막으로 한 번 더 동기화할 때까지 다시 동시에 실행됩니다.

엔트리는 단순한 태스크 동기화 이상의 작업을 수행하는 데 사용될 수 있습니다. 즉, 두 태스크가 동기화되어 있는 동안 여러 문장을 수행할 수도 있습니다. 이는 do ... end 블록으로 수행합니다. 이전 예제의 경우, 간단히 accept Start do <문장들>; end; 와 같이 작성하면 됩니다. 다음 예제에서 이런 종류의 블록을 사용합니다.

Select 루프

엔트리가 수락될 수 있는 횟수에는 제한이 없습니다. 태스크 안에 무한 루프를 만들고 동일한 엔트리에 대한 호출을 계속해서 수락할 수도 있습니다. 하지만 무한 루프는 서브태스크가 종료되는 것을 막으므로, 마스터 태스크가 자신의 처리 끝에 도달했을 때 마스터 태스크를 블록(block)시킵니다. 따라서 태스크 본문에서 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;Code language: Ada (ada)
  • 이 예제에서는 태스크 본문이 ResetIncrement 엔트리에 대한 호출을 수락하는 무한 루프를 구현합니다. 다음과 같은 점들을 살펴봅니다:
    • 태스크 Tdo ... end 블록을 수행하는 동안, 메인 태스크는 그 블록이 완료되기를 기다립니다.
  • 메인 태스크는 1 .. 4 루프 안에서 Increment 엔트리를 여러 번 호출하고 있습니다. 또한 두 번째 루프 전에 Reset 엔트리를 호출하고 있습니다.
    • 태스크 T가 무한 루프를 포함하고 있기 때문에, 그것은 항상 ResetIncrement 엔트리에 대한 호출을 수락합니다.
    • 서브태스크의 마스터 구조(Show_Rendezvous_Loop 서브프로그램)가 완료되면, 그것은 T 태스크의 상태를 확인합니다. 비록 태스크 TReset 또는 Increment 엔트리에 대한 새로운 호출을 수락할 수 있더라도, select 문의 or terminate 부분 때문에 마스터 구조는 태스크 T를 종료시킬 수 있습니다.

주기적인 태스크 (Cycling tasks)

이전 예제에서 delay 키워드를 사용하여 지정된 시간만큼 태스크를 지연시키는 방법을 보았습니다. 하지만 루프 안에서 delay 문을 사용하는 것은 해당 delay 문들 사이의 규칙적인 간격을 보장하기에 충분하지 않습니다. 예를 들어, 연속적인 delay 문의 실행 사이에 계산량이 많은 프로시저에 대한 호출이 있을 수 있습니다:

while True loop
   delay 1.0;
   --    ^ 1.0초 대기
   Computational_Intensive_App;
end loop;Code language: Ada (ada)

이 경우, Computational_Intensive_App 프로시저에 의해 시간 드리프트(time drift)가 발생할 수 있기 때문에 delay 문을 10번 호출한 후에 정확히 10초가 경과했다고 보장할 수 없습니다. 많은 경우, 이 시간 드리프트는 중요하지 않으므로 delay 키워드를 사용하는 것으로 충분합니다.

하지만 시간 드리프트가 허용되지 않는 상황도 있습니다. 그런 경우에는 delay until 문을 사용해야 합니다. 이것은 지연이 끝나는 정확한 시간을 받으므로 규칙적인 간격을 정의할 수 있게 해줍니다. 예를 들어, 이는 실시간 애플리케이션에서 유용합니다.

곧 이 시간 드리프트가 어떻게 발생할 수 있는지, 그리고 delay until 문이 어떻게 이 문제를 회피하는지에 대한 예제를 보게 될 것입니다. 하지만 그 전에, 경과 시간을 측정할 수 있게 해주는 프로시저(Show_Elapsed_Time)와 간단한 지연(delay)을 사용하여 시뮬레이션되는 더미(dummy) Computational_Intensive_App 프로시저를 포함하는 패키지를 살펴봅니다. 이것이 전체 패키지입니다:

delay_aux_pkg.ads

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;Code language: Ada (ada)

delay_aux_pkg.adb

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;Code language: Ada (ada)

이 보조 패키지를 사용하여, 이제 시간 드리프트가 발생하는 애플리케이션을 작성할 준비가 되었습니다:

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;Code language: Ada (ada)

애플리케이션을 실행해보면 Computational_Intensive_App에 의해 발생한 드리프트 때문에 루프를 세 번 반복한 후에 이미 약 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;Code language: Ada (ada)

이제 애플리케이션을 실행해 보면 알 수 있듯이, delay until 문은 Computational_Intensive_App 작업이 있더라도 각 반복 사이의 1초라는 규칙적인 간격이 유지되도록 보장합니다.

보호 객체 (Protected objects)

여러 태스크가 공유 데이터에 접근할 때, 해당 데이터의 손상이 발생할 수 있습니다. 예를 들어, 한 태스크가 다른 태스크가 동시에 읽고 있는 정보의 일부를 덮어쓰면 데이터가 일관성을 잃을 수 있습니다(데이터 불일치 발생). 이러한 종류의 문제를 피하고 정보가 조율된 방식으로 접근되도록 보장하기 위해, 우리는 보호 객체(protected objects)를 사용합니다.

보호 객체는 데이터를 캡슐화하고, 서브프로그램(subprograms)이나 보호 엔트리(protected entries) 형태인 보호 오퍼레이션(protected operations)을 통해 해당 데이터에 대한 접근을 제공합니다. 보호 객체를 사용하면 경쟁 상태(race conditions)나 다른 병행 접근(concurrent access)으로 인해 데이터가 손상되지 않음을 보장합니다.

중요

Ada 태스크를 사용하여 동시 접근으로부터 객체를 보호할 수도 있습니다. 사실, 이것이 Ada 언어의 첫 번째 버전인 Ada 83에서 동시 접근으로부터 객체를 보호하는 유일한 방법이었습니다. 그러나 보호 객체(protected objects)를 사용하는 것이 오직 태스크만을 사용하여 구현된 유사한 메커니즘보다 훨씬 간단합니다. 따라서 주된 목표가 단지 데이터 보호라면 보호 객체를 사용해야 합니다.

단순 객체

protected 키워드를 사용하여 보호 객체를 선언합니다. 그 구문은 패키지에 사용되는 것과 유사합니다: 공개 부분(public part)에는 오퍼레이션(예: 프로시저, 함수)을 선언하고, 비공개 부분(private part)에는 데이터를 선언할 수 있습니다. 오퍼레이션의 해당 구현은 객체의 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 ("숫자는: "
             & Integer'Image (Obj.Get));
end Show_Protected_Objects;Code language: Ada (ada)

이 예제에서는 Obj에 대해 SetGet이라는 두 개의 오퍼레이션을 정의합니다. 이 오퍼레이션들의 구현은 Obj 몸체(body) 안에 있습니다. 이 오퍼레이션들을 작성하는 데 사용된 구문은 일반적인 프로시저 및 함수와 동일합니다. 보호 객체의 구현은 간단합니다 — 해당 서브프로그램 내에서 단순히 Local 데이터에 접근하고 업데이트하면 됩니다. 메인 애플리케이션에서 이 오퍼레이션들을 호출하려면, Obj.Get과 같이 접두사 표기법(prefixed notation, 점 표기법)을 사용합니다.

엔트리 (Entries)

보호 프로시저와 함수 외에도, 보호 엔트리(protected entry points)를 정의할 수도 있습니다. 이는 entry 키워드를 사용하여 수행합니다. 보호 엔트리를 사용하면 when 키워드를 통해 배리어(barriers)를 정의할 수 있습니다. 배리어는 엔트리가 실제 처리를 시작하기 전에 충족되어야 하는 조건입니다 — 조건이 충족되면 배리어가 해제되었다고 말합니다.

이전 예제에서는 보호 객체의 오퍼레이션을 정의하기 위해 프로시저와 함수를 사용했습니다. 하지만 그렇게 하면 정보가 설정되기 전(Obj.Set 호출 전)에 보호된 정보를 읽는 것(Obj.Get 호출)이 허용됩니다. 이를 정의된 연산으로 허용하기 위해 기본값(0)을 지정했습니다. 대신, Obj.Get을 함수 대신 entry를 사용하여 다시 작성함으로써, 우리는 배리어를 구현하여 어떤 태스크도 정보가 설정되기 전에는 읽을 수 없도록 보장합니다.

다음 예제는 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
        ("태스크 T가 4초 동안 지연됩니다...");
      delay 4.0;

      Put_Line
        ("태스크 T가 Obj를 설정합니다...");
      Obj.Set (5);

      Put_Line
        ("태스크 T가 방금 Obj를 설정했습니다...");
   end T;
begin
   Put_Line
     ("메인 애플리케이션이 Obj를 가져옵니다...");
   Obj.Get (N);

   Put_Line
     ("메인 애플리케이션이 Obj를 가져왔습니다...");
   Put_Line
     ("숫자는: " & Integer'Image (N));

end Show_Protected_Objects_Entries;Code language: Ada (ada)

실행해 보면 알 수 있듯이, 메인 애플리케이션은 (태스크 T에서의 Obj.Set 호출에 의해) 보호 객체가 설정될 때까지 기다렸다가 (Obj.Get을 통해) 정보를 읽습니다. 태스크 T에 4초의 지연이 추가되었기 때문에, 메인 애플리케이션 역시 4초 동안 지연됩니다. 이 지연 후에야 비로소 태스크 T가 객체를 설정하고 Obj.Get의 배리어를 해제하며, 그 결과 메인 애플리케이션이 (보호 객체로부터 정보를 가져온 후) 처리를 재개할 수 있습니다.

태스크 및 보호 타입

이전 예제들에서는 단일 태스크와 보호 객체를 정의했습니다. 하지만 타입 정의를 사용하여 태스크와 보호 객체를 일반화할 수 있습니다. 예를 들어, 이를 통해 단 하나의 태스크 타입을 기반으로 여러 태스크를 생성할 수 있습니다.

태스크 타입 (Task types)

태스크 타입은 태스크의 일반화입니다. 선언은 단순 태스크와 유사합니다: tasktask 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;Code language: Ada (ada)

이제 task Ttask 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;Code language: Ada (ada)

이 예제를 확장해서 태스크의 배열을 만들 수 있습니다. 변수 선언과 문법이 같기 때문에, 태스크 타입에도 비슷한 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;Code language: Ada (ada)

이 예제에서는 My_Tasks 배열에 5개의 태스크를 선언합니다. (Start) 엔트리를 통해 각 태스크에 배열 인덱스를 전달합니다. 개별 서브태스크와 메인 태스크가 동기화된 후, 각 서브태스크는 병행적으로 Put_Line을 호출합니다.

보호 타입 (Protected types)

보호 타입은 보호 객체의 일반화입니다. 선언은 보호 객체와 유사합니다: protectedprotected 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;Code language: Ada (ada)

이 예제에서는 보호 객체 Obj를 직접 정의하는 대신, 먼저 보호 타입 P_Obj_Type을 정의한 다음 Obj를 이 보호 타입의 객체로 선언합니다. 참고로 메인 애플리케이션 코드는 변경되지 않았습니다. 기존 예제와 마찬가지로 보호 객체에 접근하기 위해 여전히 Obj.SetObj.Get을 사용합니다.


이 문서는 ‘Introduction to Ada'(저자: Raphaël Amiard, Gustavo A. Hoffmann, © AdaCore 2018–2024)를 Google Gemini가 한국어로 번역한 것입니다. 원저작물과 동일하게 CC BY 4.0 라이선스가 적용됩니다.


게시됨

카테고리

작성자

태그:

댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다