In this section, we will explore the concepts of synchronization and communication in Ada, which are crucial for developing concurrent and real-time systems. We will cover the following topics:

  1. Introduction to Synchronization and Communication
  2. Protected Objects
  3. Task Synchronization
  4. Inter-Task Communication
  5. Practical Examples
  6. Exercises

  1. Introduction to Synchronization and Communication

Synchronization and communication are essential in concurrent programming to ensure that tasks (threads) can work together without conflicts and share data safely. Ada provides robust mechanisms to handle these aspects efficiently.

  1. Protected Objects

Protected objects in Ada are used to ensure mutual exclusion and synchronization between tasks. They encapsulate data and provide synchronized access to it through protected operations.

Key Concepts:

  • Protected Type: A type that defines a protected object.
  • Protected Operations: Procedures, functions, and entries that provide synchronized access to the protected data.

Example:

protected type Shared_Counter is
   procedure Increment;
   function Get_Value return Integer;
private
   Counter : Integer := 0;
end Shared_Counter;

protected body Shared_Counter is
   procedure Increment is
   begin
      Counter := Counter + 1;
   end Increment;

   function Get_Value return Integer is
   begin
      return Counter;
   end Get_Value;
end Shared_Counter;

In this example, Shared_Counter is a protected type with an Increment procedure and a Get_Value function. The Counter variable is protected and can only be accessed through these operations.

  1. Task Synchronization

Ada provides several mechanisms for task synchronization, including protected objects, rendezvous, and delay statements.

Rendezvous:

A rendezvous is a synchronization mechanism where one task waits for another to reach a specific point of execution.

Example:

task type Server is
   entry Process_Request;
end Server;

task body Server is
begin
   accept Process_Request do
      -- Process the request
   end Process_Request;
end Server;

task body Client is
begin
   Server.Process_Request;
end Client;

In this example, the Server task has an entry Process_Request, and the Client task calls this entry, causing a rendezvous.

  1. Inter-Task Communication

Inter-task communication in Ada can be achieved using protected objects, message passing, and shared variables.

Message Passing:

Tasks can communicate by sending and receiving messages through entries.

Example:

task type Producer is
   entry Send_Message (Msg : in String);
end Producer;

task type Consumer is
   entry Receive_Message (Msg : out String);
end Consumer;

task body Producer is
begin
   Consumer.Receive_Message ("Hello from Producer");
end Producer;

task body Consumer is
   Msg : String (1 .. 20);
begin
   accept Receive_Message (Msg : out String) do
      Msg := "Hello from Producer";
   end Receive_Message;
end Consumer;

In this example, the Producer task sends a message to the Consumer task using the Receive_Message entry.

  1. Practical Examples

Example 1: Using Protected Objects for Synchronization

with Ada.Text_IO; use Ada.Text_IO;

protected type Shared_Buffer is
   procedure Write (Item : in Integer);
   function Read return Integer;
private
   Buffer : Integer := 0;
   Full : Boolean := False;
end Shared_Buffer;

protected body Shared_Buffer is
   procedure Write (Item : in Integer) is
   begin
      if not Full then
         Buffer := Item;
         Full := True;
      end if;
   end Write;

   function Read return Integer is
   begin
      if Full then
         Full := False;
         return Buffer;
      else
         return -1; -- Indicate buffer is empty
      end if;
   end Read;
end Shared_Buffer;

task type Producer (Buffer : access Shared_Buffer) is
end Producer;

task body Producer is
begin
   for I in 1 .. 10 loop
      Buffer.Write (I);
      delay 0.1;
   end loop;
end Producer;

task type Consumer (Buffer : access Shared_Buffer) is
end Consumer;

task body Consumer is
   Item : Integer;
begin
   loop
      Item := Buffer.Read;
      if Item /= -1 then
         Put_Line ("Consumed: " & Integer'Image (Item));
      end if;
      delay 0.2;
   end loop;
end Consumer;

procedure Main is
   Shared : aliased Shared_Buffer;
   Prod : Producer (Buffer => Shared'Access);
   Cons : Consumer (Buffer => Shared'Access);
begin
   null;
end Main;

Explanation:

  • Shared_Buffer is a protected object that ensures synchronized access to a buffer.
  • Producer writes to the buffer, and Consumer reads from it.
  • The Main procedure creates instances of Producer and Consumer tasks, sharing the Shared_Buffer.

  1. Exercises

Exercise 1: Implement a Bounded Buffer

Create a bounded buffer using protected objects. The buffer should have a fixed size, and producers should wait if the buffer is full, while consumers should wait if the buffer is empty.

Solution:

protected type Bounded_Buffer (Size : Positive) is
   entry Write (Item : in Integer);
   entry Read (Item : out Integer);
private
   Buffer : array (1 .. Size) of Integer;
   Count : Natural := 0;
   In_Index : Positive := 1;
   Out_Index : Positive := 1;
end Bounded_Buffer;

protected body Bounded_Buffer is
   entry Write (Item : in Integer) when Count < Size is
   begin
      Buffer (In_Index) := Item;
      In_Index := (In_Index mod Size) + 1;
      Count := Count + 1;
   end Write;

   entry Read (Item : out Integer) when Count > 0 is
   begin
      Item := Buffer (Out_Index);
      Out_Index := (Out_Index mod Size) + 1;
      Count := Count - 1;
   end Read;
end Bounded_Buffer;

task type Producer (Buffer : access Bounded_Buffer) is
end Producer;

task body Producer is
begin
   for I in 1 .. 20 loop
      Buffer.Write (I);
      delay 0.1;
   end loop;
end Producer;

task type Consumer (Buffer : access Bounded_Buffer) is
end Consumer;

task body Consumer is
   Item : Integer;
begin
   loop
      Buffer.Read (Item);
      Put_Line ("Consumed: " & Integer'Image (Item));
      delay 0.2;
   end loop;
end Consumer;

procedure Main is
   Shared : aliased Bounded_Buffer (Size => 5);
   Prod : Producer (Buffer => Shared'Access);
   Cons : Consumer (Buffer => Shared'Access);
begin
   null;
end Main;

Explanation:

  • Bounded_Buffer is a protected object with a fixed size.
  • Write and Read entries ensure that producers wait if the buffer is full and consumers wait if the buffer is empty.
  • The Main procedure creates instances of Producer and Consumer tasks, sharing the Bounded_Buffer.

Conclusion

In this section, we covered the essential concepts of synchronization and communication in Ada, including protected objects, task synchronization, and inter-task communication. We provided practical examples and exercises to reinforce the learned concepts. Understanding these mechanisms is crucial for developing robust concurrent and real-time systems in Ada. In the next module, we will delve into advanced topics such as exception handling and input/output operations.

© Copyright 2024. All rights reserved