views:

136

answers:

3

Hi

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)

+5  A: 

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.

Simon Wright
Many thanks ! ..... so, how do we determine the order ? is it with pragmas ?
Arkapravo
How do you do the 'Protected' ..... using semaphores/ mutexes ???
Arkapravo
Well, I don't "do" the 'protected', the Ada compiler does it using what is appropriate on the system concerned. For example, there's an Unlock procedure deep in the Ada RTS that on Windows uses LeaveCriticalSection(), but on VxWorks uses semGive().As for the order, I guess it depends on why you want these three things to happen one after the other. The simplest way would be not to use tasks in the first place! But if you want (say) the three tasks to proceed in parallel and then synchronise in order, you'd need to use entries in the protected object. Will update my answer later.
Simon Wright
+1  A: 

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.

T.E.D.
@ T.E.D : Thanks ! ...I will give another thought to the program... I guess we should have more concurrency related discussions, Ada is meant for that ! ... Ada without concurrency is more Pascal-ish ! ...
Arkapravo
@ T.E.D: I tried to 'protect' each single task using semaphores .... and kept the same pragma ordering ..... however, nothing seems to have changed .... can you do me a favour and write a concurrent code which correctly orders the tasks, thus a solution to the 'racing problem' ! ... many thanks ...
Arkapravo
@ T.E.D : You may also wish to participate in the discussion (above in this page) where I seem to have drawn some flack for using tasks ! :)
Arkapravo
I didn't participate because I agreed with the comments. I was one of the upvotes. The simplest way to get the behavior you are looking for is to just write a single procedure to invoke the updates in the order you want. I'm assuming you are really looking to do something between those points that makes use of concurrency, otherwise, Marc C. is exactly right.
T.E.D.
A: 

Hi guys, 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.

Arkapravo