Arkapravo
Arkapravo

Reputation: 4094

Issues in Ada Concurrency

I need some help and also some insight. This is a program in Ada-2005 which has 3 tasks. The output is 'z'. If the 3 tasks do not happen in the order of their placement in the program then output can vary from z = 2, z = 1 to z = 0 ( That is easy to see in the program, mutual exclusion is attempted to make sure output is z = 2).

WITH Ada.Text_IO; USE Ada.Text_IO;
WITH Ada.Integer_Text_IO; USE Ada.Integer_Text_IO; 
WITH System; USE System;

procedure xyz is 
   x : Integer := 0; 
   y : Integer := 0; 
   z : Integer := 0;

   task task1 is
      pragma Priority(System.Default_Priority + 3);
   end task1;

   task task2 is
      pragma Priority(System.Default_Priority + 2);
   end task2;

   task task3 is
      pragma Priority(System.Default_Priority + 1);
   end task3;

   task body task1 is
   begin
      x := x + 1;
   end task1;

   task body task2 is
   begin
      y := x + y;
   end task2;

   task body task3 is
   begin
      z := x + y + z;
   end task3;

begin 
   Put(" z = ");
   Put(z); 
end xyz;

I first tried this program

(a) without pragmas, the result : In 100 tries, occurence of 2: 86, occurence of 1: 10, occurence of 0: 4.

Then

(b) with pragmas, the result : In 100 tries, occurence of 2: 84, occurence of 1 : 14, occurence of 0: 2.

Which is unexpected as the 2 results are nearly identical. Which means pragmas or no pragmas the output has same behavior.

Those who are Ada concurrency Gurus please shed some light on this topic. Alternative solutions with semaphores (if possible) is also invited.

Further in my opinion for a critical process (that is what we do with Ada), with pragmas the result should be z = 2, 100% at all times, hence or otherwise this program should be termed as 85% critical !!!! (That should not be so with Ada)

Upvotes: 2

Views: 846

Answers (3)

Arkapravo
Arkapravo

Reputation: 4094

here is a program that is a sequential variant of the above ! .... with just one task (even that can be avoided if I use a single procedure, as suggested by Marc C and T.E.D )

WITH Ada.Text_IO; USE Ada.Text_IO;

WITH Ada.Integer_Text_IO; USE Ada.Integer_Text_IO;

WITH System; USE System;

procedure xyzsimple is

x : Integer := 0; y : Integer := 0; z : Integer := 0;

task type xyz;

T: xyz;

task body xyz is

begin

x:= x + 1;
y:= x + y;
z:= x + y + z;

end xyz;

begin Put(" z = ");

Put(z);

end xyzsimple;

This program always gvies output z = 2, but then it doesn't serve to illustrate what I was trying to do. This program is deterministic, not into the paradigm of concurrency ! Further, this program will never exhibit 'RACE CONDITION' that T.E.D had mentioned.

Upvotes: 0

T.E.D.
T.E.D.

Reputation: 44814

Well, those pragmas just prioritize the tasks on your system, they don't guarantee any kind of mutual-exclusion on those variables.

There may be some systems where that is enough. However, most Ada implementations these days map Ada tasks to OS threads, and most consumer PC's these days have multiple processors and can spilt their threads among them. There's nothing stopping the OS from scheduling the next lower-priority thread on your second processor while the highest priority thread is running.

This kind of behavior in a program is called a "race condition".

If you want mutual-exlusion on those variables, you need to implement that. Either give control of the variables to one task and use rendezvous to modify them from other tasks, or look into putting them into protected objects. I'd suggest the latter, as rendezvous can be much more difficult to get right. However, if you want to order the calls in a specific way, a master controller task calling rendezvous on the other tasks might be the way to go.

Upvotes: 1

Simon Wright
Simon Wright

Reputation: 25501

A protected object to do the three operations might look something like this. But note, all this does is make sure that the three variables x, y and z are consistent with the order that the updates occurred in; it says nothing about the order.

   protected P is
      procedure Update_X;
      procedure Update_Y;
      procedure Update_Z;
      function Get_Z return Integer;
   private
      X : Integer := 0;
      Y : Integer := 0;
      Z : Integer := 0;
   end P;
   protected body P is
      procedure Update_X is
      begin
         X := X + 1;
      end Update_X;
      procedure Update_Y is
      begin
         Y := Y + X;
      end Update_Y;
      procedure Update_Z is
      begin
         Z := X + Y + Z;
      end Update_Z;
      function Get_Z return Integer is
      begin
         return Z;
      end Get_Z;
   end P;

On the other hand, to make sure that the three tasks "submit their results" in the proper order, you could rewrite P so that a call to say Update_Y will block until Update_X has been called: Get_Z now has to be an entry with an out parameter rather than a function.

  protected P is
      entry Update_X;
      entry Update_Y;
      entry Update_Z;
      entry Get_Z (Result : out Integer);
   private
      X_Updated : Boolean := False;
      Y_Updated : Boolean := False;
      Z_Updated : Boolean := False;
      X : Integer := 0;
      Y : Integer := 0;
      Z : Integer := 0;
   end P;
   protected body P is
      entry Update_X when True is
      begin
         X := X + 1;
         X_Updated := True;
      end Update_X;
      entry Update_Y when X_Updated is
      begin
         Y := Y + X;
         Y_Updated := True;
      end Update_Y;
      entry Update_Z when Y_Updated is
      begin
         Z := X + Y + Z;
         Z_Updated := True;
      end Update_Z;
      entry Get_Z (Result : out Integer) when Z_Updated is
      begin
         Result := Z;
      end Get_Z;
   end P;

The three tasks can now have any priority you like. But the task that calls Update_Z will block until the other two have reported.

Upvotes: 5

Related Questions