2015-12-10 9 views
4

J'essaie d'implémenter une barrière dans Ada qui a des fonctionnalités similaires à pthread_barrier_wait de C. Ada 2012 a Ada.Synchronous_Barriers mais ce n'est pas disponible sur mon système (gnu-gnat sur Debian Lenny). Plus précisément, comment puis-je obtenir toutes les tâches en attente d'une barrière en même temps et, idéalement, faire qu'une de ces tâches fasse quelque chose de spécial, sans utiliser Ada 2012? Voici une implémentation très sous-optimale. Quelle pourrait être une meilleure approche?Ada: comportement comme pthread_barrier_wait?

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; 

sortie du programme:

$ ./foobar 
    3 
    1 
    2 

    3 
    1 
    2 

    3 
    2 
    1 
+4

Ce paquet Ada2012 est apparu dans GCC 4.7. Il est certainement présent dans GNAT GPL 2015 - je voudrais juste télécharger le paquet source et l'utiliser (probablement mieux le renommer en 'Ada_Synchronous_Barriers', pour arrêter le compilateur). Vous pouvez dire quelle version de GNAT vous avez sur votre système par 'gnatls -v', soit dit en passant. –

+0

@SimonWrite Grande solution pratique. gnatls montre 4.6 sur ma machine – aquilonis

Répondre

2

Peut-être le « attribut de comptage des entrées serait utile - est-ce le genre de chose que vous cherchez? Utiliser des ID de tâche pour faire quelque chose de différent semble raisonnable (ou s'il est suffisamment différent, vous pouvez simplement créer un nouveau type de tâche).

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       
+0

+1 L'attribut 'count' est la clé - merci. J'ai étendu votre approche ci-dessous, en utilisant une seule barrière avec deux entrées qui fonctionne dans une boucle. – aquilonis

+1

Vous pourriez jeter un oeil à la section 4.7.2 dans «Construction d'applications Parallel, Embedded et Real-Time avec Ada». En raison des règles de priorité pour les objets protégés, vous n'avez pas besoin de deux entrées. –

1

Cela étend la réponse de Leon pour atteindre la fonctionnalité souhaitée. Il utilise un objet barrière unique et signale une tâche arbitraire pour faire quelque chose de spécial.

EDIT: Aperçu de Jacob Incorporated pour simplifier encore la barrière et d'atteindre l'objectif initial

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; 

Exemple de sortie:

$ ./bar 

      1   2   3 
      3   1   2 
      1   2   3 
      1   3   2 
      3   2   1 
+2

Ceci est inutilement complexe, car les règles de priorité pour les objets protégés signifient que toutes les tâches en attente sont autorisées à exécuter l'entrée, avant que de nouvelles n'entrent dans la file d'attente. –

+0

Oh oui! Complètement oublié le progrès interne d'abord quand j'ai écrit ma réponse. – Leon