« Code parallèle » : différence entre les versions

De Wiki1000
Ligne 149 : Ligne 149 :


===Partage de transaction longue===
===Partage de transaction longue===
Les transactions longues peuvent être partagées sous réserve de ne pas exécuter de [[BatchLongTran (CM)|batch]] à l'intérieur du code parallélisé.  
Les transactions longues peuvent être partagées sous réserve de ne pas exécuter de [[BatchLongTran (CM)|BatchLongTran]] à l'intérieur du code parallélisé.  


En effet l'exécution d'un batch doit être synchronisé avec le code métier pour éviter de mettre à jour des objets dans un état incohérent.
En effet l'exécution d'un batch doit être synchronisé avec le code métier pour éviter de mettre à jour des objets dans un état incohérent.
Ligne 159 : Ligne 159 :
Dans ce scénario :
Dans ce scénario :


* Le code crée un objet puis le modifie en plusieurs étapes avant d'appeler Batch.
* Le code crée un objet puis le modifie en plusieurs étapes avant d'appeler BatchLongTran.
* L'exécution du batch par l'exécution parallèle (worker2) met à jour un objet en cours de création (worker1) dans un état incohérent.
* L'exécution du batch par l'exécution parallèle (worker2) met à jour un objet en cours de création (worker1) dans un état incohérent.


{{tip|Le pattern de code ''ForEachP with long transaction'' effectue un batch à l'intérieur de la boucle "each". Avec ce pattern vous n'avez pas besoin d'appeler explicitement le batch.}}
Le comportement des patterns de code vis à vis des transactions longues est le suivant :
 
'''ForEachP with long(BatchSize) transaction'''
: Ce pattern exécute BatchLongTran à l'intérieur de sa boucle, vous n'avez pas besoin d'appeler explicitement BatchLongTran.
 
'''WithP long(BatchSize) transaction'''
: Ce pattern n'appelle pas BatchLongTran et vous devez appeler explicitement BatchLongTran à l'extérieur du code parallélisé.
 
{{tip|
* Un appel à BatchLongTran est ignoré si le nombre d'objet dans la transaction est inférieur à la taille du batch (BatchSize)
* Un appel à BatchLongTran depuis du code parallélisé est ignoré.
}}


===Messages émis par le code===
===Messages émis par le code===

Version du 11 octobre 2011 à 14:58

stock}}

La programmation parallèle permet de distribuer des boucles de traitement sur l'ensemble des coeurs disponibles.

Près-requis

Pour être parallélisable une boucle de traitement doit vérifier les conditions suivantes :

  • Chaque exécution du corps de boucle doit être indépendante.
Il n'existe pas de mécanisme de synchronisation de variable de sorte que les exécutions parallèles ne doivent pas partager d'objet ou de variable.
Les patterns de code implémentent le mécanisme le synchronisation des différents exécuteurs.
  • La boucle de traitement doit appeler une méthode qui exécute la tâche.
Ce sont l'exécution des méthodes qui sont parallélisées.

Pool d'exécuteur

L'exécution d'une méthode en parallèle est pris en charge par des exécuteurs (working thread) qui sont gérés en pool. La taille de ce pool est déterminée par le nombre de coeurs disponibles et la configuration réseau.

La taille de ce pool est déterminée automatiquement, toutefois à des fins de test il est possible de changer celle-ci dans le dialogue de préférence du concepteur de modèle onglet "Code parallèle"

Mot clé "parallel"

Ce mot clé se place devant l'appel d'une méthode pour indiquer au compilateur de générer l'appel parallèle.

<source lang="delphi"> var tk:Int64; begin

 tk := GetTickCount;
 try
 // use a pattern to sync the workers
 withP Transaction do
 for var idx:=1 to 10 do
  begin
   parallel doCreateA(idx);
  end;
 finally
 tk := GetTickCount-tk;
 showmessage(Format('%s ms',[TickToString(tk)]));
 end;

end; </source>

Paramètres des méthodes parallélisées

Lorsqu'une méthode est exécutée en parallèle ses paramètres sont copiés dans le contexte de l'exécuteur.

Certain paramètres sont gérés avec attention :

  • Les objets sont tenus par référence et non pas par copie.
  • les liste d'objet sont dupliquées (mais pas les objets qu'elles contiennent)

Exemple :

Dans cet exemple une liste d'objet a traité est passé à la méthode doProcessList, cette liste est ensuite réinitialisée sans que cela impacte l'exécution en cours de l'exécuteur.

<source lang="delphi"> var list:WFClasseAList; inst:WFClasseA; cursor:WFClasseACursor; count:Integer; tk:Int64; begin

 List := WFClasseA.CreateList;
 Cursor := WFClasseA.CreateCursorWhere(,,true,['A',1]);
 tk := GetTickCount;
 try
 withP long(100) transaction
  begin
   foreachP inst in cursor index count do
    begin
      List.AddRef(inst);
      if List.Count=100 then
       begin
         // start a working thread
         parallel doProcessList(List);
         // list.Clear is safe because the list has been cloned
         List.Clear;
       end;
    end;
   if List.Count<>0 then doProcessList(List); 
  end;
 finally
 tk := GetTickCount-tk;
 showmessage(Format('%d %s ms',[count,TickToString(tk)]));
 end;

end; </source>

Partage de transaction

Les transactions objets peuvent être partagées lors d'une exécution parallèle.

Exemples :

Une seule transaction partagée :

<source lang="delphi"> //Procedure doCreateA(index:Integer); var inst:WFClasseA; begin

 // share the transaction
 inst := WFClasseA.Create;
 inst.unCode := 'X'+inttostr(index);
 inst.Caption := 'Objet A'+inttostr(index);
 inst.unEntier := 1+Trunc(Random(100));

end;

//Procedure CreateSomeA; var tk:Int64; begin

 tk := GetTickCount;
 try
 withP Transaction do
 for var idx:=1 to 10 do
  begin
   parallel doCreateA(idx);
  end;
 finally
 tk := GetTickCount-tk;
 showmessage(Format('%s ms',[TickToString(tk)]));
 end;

end; </source>

Un transaction par exécuteur :

<source lang="delphi"> //Procedure doProcess(inst:WFClasseA); var inst:WFClasseA; begin

 // One separate transaction
 withP  private Transaction do
  begin
    inst.unCode := 'X'+inttostr(index);
    inst.Caption := 'Objet A'+inttostr(index);
    inst.unEntier := 1+Trunc(Random(100));
  end;

end;

//Procedure ProcessSomeA(AList:WFClasseAList); var tk:Int64; begin

 tk := GetTickCount;
 try
 foreachP var inst in AList do 
  begin
   parallel dProcess(inst);
  end;
 finally
 tk := GetTickCount-tk;
 showmessage(Format('%s ms',[TickToString(tk)]));
 end;

end; </source>

Partage de transaction longue

Les transactions longues peuvent être partagées sous réserve de ne pas exécuter de BatchLongTran à l'intérieur du code parallélisé.

En effet l'exécution d'un batch doit être synchronisé avec le code métier pour éviter de mettre à jour des objets dans un état incohérent.

A défaut le scénario suivant peut se produire :

{{#images:batch-problem.png|dsm/parallel}}

Dans ce scénario :

  • Le code crée un objet puis le modifie en plusieurs étapes avant d'appeler BatchLongTran.
  • L'exécution du batch par l'exécution parallèle (worker2) met à jour un objet en cours de création (worker1) dans un état incohérent.

Le comportement des patterns de code vis à vis des transactions longues est le suivant :

ForEachP with long(BatchSize) transaction

Ce pattern exécute BatchLongTran à l'intérieur de sa boucle, vous n'avez pas besoin d'appeler explicitement BatchLongTran.

WithP long(BatchSize) transaction

Ce pattern n'appelle pas BatchLongTran et vous devez appeler explicitement BatchLongTran à l'extérieur du code parallélisé.
Tip :
  • Un appel à BatchLongTran est ignoré si le nombre d'objet dans la transaction est inférieur à la taille du batch (BatchSize)
  • Un appel à BatchLongTran depuis du code parallélisé est ignoré.

Messages émis par le code

Le code exécuté en parallèle peut émettre des messages vers l'interface utilisateur.

Tip :
  • Du fait du parallélisme l'ordre d'apparition des messages ne peut pas être garanti.
  • La collecte des messages est réalisée par une thread qui se synchronise avec l'interface utilisateur.

Interruption du traitement

Lorsqu'une boucle parallélisée est interrompue, par exemple par une instruction break, l'interruption ne sera effective qu'à la fin des exécutions déjà démarrées. En d'autre terme les exécutions déjà lancées se poursuivent.

<source lang="delphi"> begin

 ...
 foreachP I in C index ACount on except continue do
  begin
    // check abort
    if UserAbort then
     begin
       doLogFailure(nil,S,_TP('Traitement interrompu'));
       break;
     end;
    ....
  end;
  // all working thread terminated even if a break has occured.

end; </source>

Gestion des exceptions

Les exceptions qui se produisent dans un exécuteur sont capturées, la prise en compte dans la boucle principale dépend du mode de gestion de celle-ci.

  • Mode break
L'exception arrête la boucle est re-déclenchée dans la boucle principale (main thread)
  • Mode continue
L'exécution est capturée et ignorée.

Imbrication

L'exécution parallèle peut être imbriquée, c'est à dire qu'une méthode exécutée en parallèle peut elle même exécutée une boucle parallélisée.