Code parallèle

De Wiki1000
versiontahoe-48x48.png

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émente 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é par le nombre de coeurs disponibles et la configuration réseau.

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

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.

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;

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.

var list:WFClasseAList; inst:WFClasseA; cursor:WFClasseACursor; count:Integer; tk:Int64;
begin
  List := WFClasseA.CreateList;
  Cursor := WFClasseA.CreateCursorWhere('','',true,['A',1]);
  tk := GetTickCount;
  try
  foreachP inst in cursor index count with long transaction 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;
  finally
  tk := GetTickCount-tk;
  showmessage(Format('%d %s ms',[count,TickToString(tk)]));
  end;
end;

Partage de transaction

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

Exemple : Une seul transaction partagée :

//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;

Exemple : Un transaction par exécuteur :

//Procedure doCreateA(index:Integer);
var inst:WFClasseA;
begin
  // share the transaction
  withP  Transaction do
   begin
     inst := WFClasseA.Create;
     inst.unCode := 'X'+inttostr(index);
     inst.Caption := 'Objet A'+inttostr(index);
     inst.unEntier := 1+Trunc(Random(100));
   end;
end;

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

Messages émis par le code

Le code exécuté en parallèle peut [ProgressMessage_(ihm)|émettre des messages] vers l'interface utilisateur.

Du fait du parallélisme l'ordre d'apparition des messages ne peut pas être garanti.

Tip : 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.

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;

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 qu'une méthode exécutée en parallèle peut elle même exécutée une boucle parallélisée.