4

I'm trying to implement a barrier in Ada which has similar functionality to C's pthread_barrier_wait. Ada 2012 has Ada.Synchronous_Barriers but that's not available on my system (gnu-gnat on debian lenny).

More specifically, how can I get all waiting tasks to be released from a barrier at ~ the same time and, ideally, have one of these tasks do something special, without using Ada 2012? Below is a very sub-optimal implementation. What might be a better approach?

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure foobar is
   protected Synchronizer is
      entry Ready_For_Action; -- prepares for tasks to wait at barrier
      entry Wait_For_Release; -- barrier
      -- do work here
      entry Done;             -- signals that all tasks are done
      entry Wait_For_Others;  -- prepares for prepare to wait at barrier
   private
      ready, active: Natural := 0;  
      -- two state variables seem to be needed as entry conditions can't
      --    safely modify the condition variable as that influences wait
      --    state in other tasks
   end Synchronizer;

   NUM_OBJECTS: constant := 3;

   protected body Synchronizer is
      entry Ready_For_Action when active = 0 is
      begin
         ready := ready + 1;
      end Ready_For_Action;
      --
      entry Wait_For_Release when ready = NUM_OBJECTS is
      begin
         active := active + 1;
      end Wait_For_Release;
      --
      entry Done when active = NUM_OBJECTS is
      begin
         ready := ready - 1;
      end Done;
      --
      entry Wait_For_Others when ready = 0 is
      begin
         active := active - 1;
      end wait_for_others;
      --
   end Synchronizer;

   task type Foo(N: Natural);

   task body Foo is
      id: Natural := N;
   begin
      for iter in 1..3 loop
         Synchronizer.Ready_For_Action;
         Synchronizer.Wait_For_Release;
         -- task N doing something special
         if id = 1 then new_line; end if;
         -- do stuff here
         delay 0.1;
         put(id); new_line;
         -- re-sync
         Synchronizer.Done;
         Synchronizer.Wait_For_Others;
      end loop;
   end Foo;
   Task1: Foo(1);
   Task2: Foo(2);
   Task3: Foo(3);
begin
   Null;
end foobar;

Program output:

$ ./foobar 
  3
  1
  2

  3
  1
  2

  3
  2
  1
aquilonis
  • 63
  • 5
  • 4
    This Ada2012 package appeared in GCC 4.7. It’s certainly present in GNAT GPL 2015 - I’d just download the source package and use that (probably best rename it to `Ada_Synchronous_Barriers`, though, to stop the compiler getting confused). You can tell what version of GNAT you have on your system by `gnatls -v`, by the way. – Simon Wright Dec 10 '15 at 22:03
  • @SimonWrite Great pracitcal solution. gnatls shows 4.6 on my machine – aquilonis Dec 11 '15 at 08:30

2 Answers2

2

Maybe the 'count attribute on entries would be useful - is this the kind of thing you're looking for? Using task IDs to make one do something different seems sensible (or if it is sufficiently different you could just make a new task type).

No_Of_Tasks : Natural := 3;
   --
protected Barrier is
   entry Continue;
private
   Released : Boolean := False;
end Barrier
   --
protected body Barrier is 
   entry Continue when (Released or else Continue'count = No_Of_Tasks)
      Released := Continue'count > 0; -- the last task locks the barrier again
   end Continue                       
end Barrier                           
wilx
  • 17,697
  • 6
  • 59
  • 114
Leon
  • 68
  • 5
  • +1 The 'count attribute is the key -- thank you. I've extended your approach below, using a single barrier with two entries that works in a loop – aquilonis Dec 11 '15 at 08:51
  • 1
    You might want to take a look at section 4.7.2 in "Building Parallel, Embedded, and Real-Time Applications with Ada". Due to the priority rules for protected objects, you don't need two entries. – Jacob Sparre Andersen Dec 11 '15 at 09:07
1

This extends Leon's answer to achieve the desired functionality. It uses a single barrier object and flags one arbitrary task to do something special.

EDIT: Incorporated Jacob's insight to further simplify barrier and achieve original goal

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure bar2 is
   NUM_TASKS: constant := 3;

   protected Barrier is
      entry Wait_For_Release(the_chosen_one: out Boolean);
   private
      released: Boolean := False;
   end Barrier;

   protected body Barrier is
      entry Wait_For_Release(the_chosen_one: out Boolean) 
         when (Released or else Wait_For_Release'count = NUM_TASKS) is
      begin
         the_chosen_one := False;
         if Wait_For_Release'count = NUM_TASKS-1 then
            the_chosen_one := True;
            released := True;
         elsif Wait_For_Release'count = 0 then
            released := False;
         end if;
      end Wait_For_Release;
   end Barrier;

   task type Foo(N: Natural);
   task body Foo is
      id: Natural := N;
      the_chosen_one: Boolean;
   begin
      for iter in 1..5 loop
         Barrier.Wait_For_Release(the_chosen_one);
         if the_chosen_one then
            new_line; 
         end if;
         put(id);     -- do stuff here
      end loop;
   end Foo;

   Task1: Foo(1);
   Task2: Foo(2);
   Task3: Foo(3);
begin
   Null;
end bar2;

Sample output:

$ ./bar 

          1          2          3
          3          1          2
          1          2          3
          1          3          2
          3          2          1
aquilonis
  • 63
  • 5
  • 2
    This is unnecessarily complex, as the priority rules for protected objects mean that all waiting tasks are allowed to run the entry, before any new ones get to enter the queue. – Jacob Sparre Andersen Dec 11 '15 at 09:17
  • Oh yes! Completely forgot about internal progress first when I wrote my answer. – Leon Dec 11 '15 at 21:36