begin process at 2008 09 06 08:20:39
1 237 654 membres
53 nouveaux aujourd'hui
14 313 membres club

Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum.
Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

[DELPHI] LES THREADS


Information sur le tutorial

Catégorie :Système Date de création : 28/08/2005 16:00:50 Vu : 13 037 fois

Note :
9,5 / 10 - par 4 personnes
9,50 / 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

Commentaire sur cette source (12)
Ajouter un commentaire et/ou une note

Description

Exécuter plusieurs tâches à la fois

Tutorial

Ils sont fondamentaux dans Windows. Alors pourquoi se priver de les utiliser ?

Contenu
         I) Description
         II) Créer et utiliser un thread
         III) Décortiquons nos écrits

I) Description

Un thread ... :
1) est un processus qui se répète indéfiniment tant qu'il n'a pas été arrêté
2) a par défaut le même flux que l'application qui l'utilise
3) est géré entièrement par Windows
4) possède une priorité (propriété
Priority), un handle (propriété Handle) et un identifiant (propriété ThreadID)

Mais surtout, une application qui utilise deux threads peut faire deux choses à la fois, ce qui n'est généralement pas possible lors que vous développez une application sans gérer de threads (car il n'y a alors qu'un seul thread). Au passage, un processeur ne sait faire qu'une chose à la fois : Windows dispatche le travail, ce qui donne l'illusion d'un travail en simultané. C'est là que la priorité joue son rôle. Quel thread doit être exécuté plus prioritairement par rapport à un autre ? Par exemple, le gestionnaire de souris est plus important que votre application, et c'est normal. Votre application aura donc une priorité inférieure.

Une haute priorité assure également une meilleure régularité de vitesse d'exécution dans le cadre d'un traitement long.

Mais si un thread effectue des opérations périodiquement, il ne peut en aucun cas être assimilé à un TTimer, en raison de ses caractéristiques, de son fonctionnement et surtout de la régularité sans faille des threads (les TTimer sont peu précis et pas toujours exécutés au bon moment, voire pas du tout si l'application est en plein travail). J'ajouterai qu'avec un thread, vous pouvez chronométrer des temps même si un gros jeu en 3D est lancé, ce que n'aurait pas permis une méthode avec un TTimer.

Conclusion : avec un thread, le traitement des données est assuré de fonctionner toujours quels que soient les processus logés en mémoire. N'hésitez pas à regarder les exemples fournis avec Delphi. Ca peut donner des idées...

 

II) Créer et utiliser un thread

J'espère que vous êtes bons sur le développement des classes, car sinon il va falloir relire le tutorial N°191.

Voici un thread fonctionnel dont le rôle est de ne rien faire... Oh ?

Créons une nouvelle unité que l'on va nommer "MonThread.pas". Copiez ce qui suit :

unit MonThread;
interface
uses
Windows, Classes, SysUtils;

type
 
TPersoThread = class(TThread)
  private
   
procedure CentralControl;
  public
   
constructor Create(CreateSuspended:boolean);
     destructor Destroy; override;
  protected
   
procedure Execute; override;
  end;

implementation

constructor
TPersoThread.Create(CreateSuspended:boolean);
begin
  inherited Create(CreateSuspended);
  FreeOnTerminate:=false;
  Priority:=tpNormal;
end;

destructor TPersoThread.Destroy;
begin
 
//déchargez la mémoire ici si vous avez créé des objets
 
inherited;
end;

procedure TPersoThread.CentralControl;
begin
 
//écrivez ici ce que doit faire votre thread à un instant T donné
end;

procedure TPersoThread.Execute;
begin
 
repeat
   
Sleep(
500); //en millisecondes
    
Synchronize(CentralControl);
  until Terminated;
end;
end.

Pour utiliser ce thread dans votre nouvelle application, il suffit de rajouter dans "Unit1.pas" tout ce qui est en rouge :

unit Unit1;
interface
uses
 
Windows, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, Dialogs
, MonThread;

type
 
TForm1 = class(TForm)
  private
   
{ Déclarations privées }
 
public
   
{ Déclarations publiques }
 
end;

var Form1 : TForm1;
   
MThrd : TMonThread;

implementation
{$R *.DFM}

initialization
 
MThrd:=TMonThread.Create(true);
finalization
 
MThrd.Free;
end.

Dans le cas ci-dessus, pour lancer le thread, il suffira de faire MThrd.Resume;
Pour suspendre le thread, il suffira de faire MThrd.Suspend;

C'est tout simple. Il faut que vous en soyez convaincu ;)

 

III) Décortiquons nos écrits

o Reprenons notre fichier "MonThread.pas". Qu'y a-t-il dedans ?

Il y a déjà un héritage depuis TThread. Pour information : TThread = class;

Le constructor public :
1) Il configure la propriété de TThread nommée FreeOnTerminate. Mieux vaut la laisser toujours sur
false en raison de la structure en INITIALIZATION...FINALIZATION de "Unit1.pas". Comme son nom l'indique, le Thread subit .Free si vous appelez .Terminate alors que FreeOnTerminate vaut true. Donc, on met false pour éviter les conflits.
2) Il y a aussi configuration de la priorité du thread. Les différentes possibilités de la plus faible à la plus forte sont les suivantes. On rappelle que haute rapidité ne rime pas avec haute vitesse d'exécution. C'est juste que si le système est occupé par certains processus, alors un
tpTimeCritical permet de réduire un peu les pertes de temps.
   tpIdle          => dans les temps morts de l'application
   tpLowest
   tpLower
   tpNormal        => valeur par défaut
   tpHigher
   tpHighest
   tpTimeCritical  => priorité absolue
3) C'est dans ce bloc qu'on initialise les variables et les objets.
4) Vous pouvez édulcorer le constructor de paramètre additionnels, tout en gardant le
inherited.

Le destructor public permet de libérer les objets qui auraient été créés dans le constructor : TStringList, TPanel... par exemple. S'il ne reste que inherited dans le destructor, vous pouvez faire disparaître le destructor (superflu).

Dans le thread, il nous faut une procédure privée qui effectuera une tâche périodiquement. Par exemple : tester l'existence d'un fichier et agir en conséquence, vérifier un paramètre système... etc.

Et enfin, il nous faut le corps crucial du thread : la procédure Execute. Il faut garder la structure qui est donnée ci-dessus. Le Sleep permet de suspendre temporairement le thread sans modifier ses propriétés internes. En fait, le Sleep est indispensable pour ne pas bloquer et figer tout le système d'exploitation. Ensuite, on appelle la procédure Synchronize dont on met en paramètre le nom de la procédure qui doit être exécutée. Vous allez me dire : pourquoi ne pas appeler directement la procédure passée en paramètre ? En fait, ça permet d'éviter les conflits du multi-threading (quand il y a plusieurs threads qui s'exécutent en parallèle). Si vous voulez paramétrer CentralControl, vous n'avez pas d'autres choix que de développer des propriétés. Enfin, on boucle Sleep et Synchronize tant que le thread est actif.

o Passons maintenant au fichier "Unit1.pas"

Ici, on utilise qu'un seul thread. On déclare l'unité-squelette, la variable thread qui sera véritablement notre thread (pour l'instant, on n'avait développé que son squelette) et enfin on crée les blocs qui vont initialiser et détruire le thread respectivement quand l'application se lance et se ferme (merci INITIALIZATION...FINALIZATION).

Insistons sur le fait qu'un TMonThread.Create(true) crée un thread initialement arrêté. Pour le lancer, on utilise la procédure Resume. De même, TMonThread.Create(false) crée un thread initialement actif et qui doit subir la procedure Suspend pour l'arrêter sans le détruire.

Tanguy ALTERT, http://altert.family.free.fr/

  • signaler à un administrateur
    Commentaire de Merkel le 11/10/2005 15:22:14

    Bonjour et merci pour le tutorial. C'est la première fois que je dois travailler avec les Threads et ca devrait bien aller. Juste une remarque sur ton code. N'aurais-tu pas fais une erreur en confondant par inattention le nom de l'unité et le type de variable ?

    Dans unit1.pas, tu as écris qu'il fallait mettre :

    MThrd : TMonThread;

    et un peu plus loin :

    MThrd:=TMonThread.Create(true);

    Je crois plutôt qu'il faut remplacer "TMonThread" par "TPersoThread", non ?

    À vérifier !

  • signaler à un administrateur
    Commentaire de cirec le 28/10/2005 11:14:10 administrateur CS

    Bonjour oh GrandVizir,

    je voudrais savoir si il y a une limite quand au nombres de Thread utilisé simultanément.

    Je m'explique, j'ai crée un compo qui scroll du texte (en version Timer il n'y a aucun problème (testé 5 sur la même fiche)),en mode Thread si je met plus de 1 compos sur la fiche mon application Frise. Donc je m'avance peut être mais je pense que c'est mon Thread qui est en cause.

    Merci et @+
    Cirec

  • signaler à un administrateur
    Commentaire de ni69 le 18/11/2005 15:37:44

    Cirec, as-tu bien utilisé la méthode Synchronize ?
    Normalement, très peu de compos de la VCL sont capables d'être utilisés avec des threads... Il faut donc utiliser la méthode Synchronize qui fait exécuter le code du thread dans l'exétron principal.

    @+
    Nico

  • signaler à un administrateur
    Commentaire de cirec le 18/11/2005 23:42:53 administrateur CS

    Oui j'ai bien utiliser la methode Synchronize

    Procedure TScrollThread.Execute;
    Begin
      //Boucler tant que l'utilisateur n'a pas
      //demandé de stopper
      While Not Terminated Do
        Synchronize(ScrollText);
    End;

    si tu veux voir le reste du code je ne peut pas le poster ici mais si tu me donne ton Email je te l'envoie volontier.
    @+
    Cirec

  • signaler à un administrateur
    Commentaire de MaXoR le 19/11/2005 00:25:12

    super source ca marche tret bien chez moi, merci ++

  • signaler à un administrateur
    Commentaire de mounjetado le 14/03/2006 11:18:20

    Et qu'en est-il pour les appli multithread et la protection des données par un TMultiReadExcluveWriteSynchronizer? je ne trouve rien à ce sujet!!! à se mettre une balle ds la tête!!!! AAAAAAAARRRRGHH!!!
    merci de m'éviter une telle extrémité :p :d
    à bientôt de lire vos réponses... ;)

  • signaler à un administrateur
    Commentaire de Forman le 27/05/2006 14:35:28

    >mounjetado:

    TCriticalSection: permet d'interdire que des portions de codes soient exécutées en même temps par plusieurs threads.

    Exemple:
    var s:string='1';

    -Le thread1 fait
    while True do
      if s[Length(s)]='1' then begin
        s:=s+'2';
        s:=s+'1';
        s:=s+'2';
      end;
    -Le thread 2 fait
    while True do
      if s[Length(s)]='2' then begin
        s:=s+'1';
        s:=s+'2';
        s:=s+'1';
      end;

    Intuitivement, au bout de plusieurs boucles, la chaine s devrait valoir:
    '121212121212121212121212121212121212...'
    Or ce n'est pas le cas!
    En effet, les threads ne sont pas des portions de code qui s'exécutent en même temps, mais des portions de codes qui se partagent du temps processeur. Par exemple, le thread 1 a droit à 100 microsecondes, puis il est momentanément interrompu et c'est au tour du thread 2 d'avoir son petit "time slice" etc...
    On peut créer autant de threads qu'on veut, Miscrodsoft recommande de ne pas lancer plus de 16 threads "actifs" (c'est à dire non suspendus par l'attente d'un événement ou autre) en même temps à moins de dégrader sérieusement les performances de toutes les applications.

    Voilà pourquoi le code plus haut n'est pas fiable, exemple de scénario:
    s vaut '1'
    -Le thread 1 teste si s[Length(s)]='1' (c'est le cas).
    Il commence à faire:
    s:=s+'2'; (s vaut donc '12')
    mais il est interrompu à ce moment là et c'est au tour du thread 2.
    -Le thread 2 teste si s[Length(s)]='2' (c'est le cas).
    Il commence à faire:
    s:=s+'1'; (s vaut donc '121')
    s:=s+'2'; (s vaut donc '1212')
    s:=s+'1'; (s vaut donc '12121')
    Il est interrompu à ce moment-là et c'est au tour du thread 1 cette fois.
    -Le thread 1 finit l'exécution de ce qu'il y avait dans le begin...end:
    s:=s+'1'; (s vaut donc '121211')
    s:=s+'2'; (s vaut donc '1212112')

    Il y a donc un problème:
    on obtient '1212112' au lieu de '12121212'

    Solution:
    var s:string='1';
    var Section:TCriticalSection;
    Section:=TCriticalSection.Create;

    -Le thread1 fait
    while True do begin
      Section.Enter;
      try
        if s[Length(s)]='1' then begin
          s:=s+'2';
          s:=s+'1';
          s:=s+'2';
        end;
      finally
        Section.Leave
      end;
    -Le thread 2 fait
    while True do
      Section.Enter;
      try
        if s[Length(s)]='2' then begin
          s:=s+'1';
          s:=s+'2';
          s:=s+'1';
        end;
      finally
        Section.Leave;
      end;

    Cette fois-ci, les 2 parties de codes protégées par la section critique (entre Section.Enter et Section.Leave) ne peuvent pas s'exécuter en même temps. Sa signifie concrètement que contrairement à l'exemple précédant, le thread 2 ne peut pas exécuter sa partie de code protégée alors que le thread 1 n'a pas fini la sienne. C'est en quelque sorte une façon de forcer le découpage du temps de processeur aux endroits où le code est protégé.

    Remarque sur les sections critiques:
    1/Toute section critique ouverte avec Enter doit être refermée avec Leave (sinon le programme risque de se bloquer: en effet un thread peut attendre indéfiniment qu'un autre ait fermé la section critique et si ça n'arrive pas alors il sera bloqué). C'est ce qu'on appelle un Dead Lock

    2/Un code propre utilise un try...finally pour refermer la section critique (en effet, si jamais il se produit une erreur dans le code protégé, la section critique ne sera pas refermée. Le finaly Section.Leave end; garantit que la section sera bien refermée, voir remarque 1/)

    3/Le programmeur doit veiller à minimiser la taille des blocks protégés au strict minimum. En effet, utiliser des sections critiques à outrance (c'est à dire protéger des parties de codes qui prennent beaucoup de temps à s'exécuter) fait perdre tout le bénéfice de l'utilisation des threads.




    TMultiReadExclusiveWriteSynchronizer:
    c'est quasiment la même chose que TCriticalSection sauf que ça permet aux threads qui utilisent une ressource en lecture seule de pouvoir tous la lire en même temps:

    -BeginWrite et EndWrite font la même chose que Enter et Leave pour une section critique: exclusion mutuelle de l'exécution 2 sections de code.

    -BeginRead et EndRead: si un thread appelle BeginRead, tous les autres threads qui lancent BeginRead ne sont pas bloqués. Néammoins, tous les threads qui appellent BeginWrite ne peuvent pas continuer avant que tous les appels des autres threads à BeginRead aient été fermés avec des appels à EndRead.

    L'idée c'est que si tu as des données que par exemple 1 thread va modifier de temps en temps, et qu'il y a beaucoup d'autres threads qui vont uniquement les LIRE (cad pas les modifier) tu vas gagner un peu de temps en laissant les threads de lecture accéder aux données simultanément.




    Quelques remarques sur TMultiReadExclusiveWriteSynchronizer:
    1/ Idem que pour les sections critiques, tout appel à BeginRead doit être suivi de EndRead (idem pour BeginWrite et EndWrite).

    2/ Idem que pour les sections critiques, utiliser try...finally...end

    3/ Il a été (très) mal programmé par Borland. Ce qui fait que le temps que tu es susceptible de gagner par rapport aux sections critiques simples risque d'être perdu par ce défaut de conception. Borland le dit quelque part je crois.

    J'en ai programmé une autre version qui va plus vite, je vais la poster si ça t'intéresse.

    4/Il peut y avoir un problème si tu exécutes le code suivant:

    TMultiReadExclusiveWriteSynchronizer.BeginRead;
    .... code qui accède à des données en lecture seule
    TMultiReadExclusiveWriteSynchronizer.BeginWrite;
    ... code qui modifie les données
    TMultiReadExclusiveWriteSynchronizer.EndWrite;
    ...
    TMultiReadExclusiveWriteSynchronizer.EndRead;

    En effet, lors de l'appel à BeginWrite, il se peut que les données aient été modifiées par un autre thread juste avant (alors que tu avais fait un BeginRead). Borland le dit clairement:

      Note: Other threads have an opportunity to modify the protected resource
      when you call BeginWrite before you are granted the write lock, even
      if you already have a read lock open.  Best policy is not to retain
      any info about the protected resource (such as count or size) across a
      write lock.  Always reacquire samples of the protected resource after
      acquiring or releasing a write lock.

    Voilà, j'espère avoir pu répondre à ta question, et avoir donné matière à compléter le tutorial!

  • signaler à un administrateur
    Commentaire de mounjetado le 05/06/2006 16:26:17

    Salut FORMAN, ;)
    je désespérais tellement d'avoir enfin qqn qui prenne le tps de me lire que j'en ai délaissé qq peu la lecture des posts...
    aussi ai-je tardé à te répondre!
    je te remercie humblement pour toutes ces informations, et je m'y réfèrerai autant que nécessaire.
    enfin quoi qu'il en soit, j'ai copié à l'instant ton unité que j'essaierai dès que possible d'utiliser...
    mais tt d'abord, un doute m'habite... voire me hante!!!
    si j'ai bien compris, on déclare une variable, et qd on l'utilise de ma nière sécurisée, on intercale le code entre un TMREWsync.BeginRead (or Write) et un TMREWSync.EndRead (or Write)??? Du style:
      MyMREWS.BeginWrite;
      try
        MyActiveMeasuresArray[FMeasuresCount mod MaxMeasuresCount] := MyMeasure;
      finally
        MyMREWS.EndWrite;
      end;
      SetMeasuresCount;

    D'autre part, j'ai un gros souci qui m'est apparu et dont j'gnore la provenance et la cause...
    En effet je remplis un Array de 32 valeurs de type Word (je te passe les détails, mais ça représente une mesure sur 32 canaux) et je stocke cet array ds un autre qui contient ttes mes mesures...
    Sauf que... ben voilà, ça me recopie non pas à l'emplacement que je désire, donné par un compteur privé à mon thread, mais depuis le début de mon array jusqu'à cette position!!!
    Mieux! Ma mesure semble être recopiée avant mm que je n'arrive à la ligne de code, en mode pas à pas...
    J'avoue que le projet est qq peu bizarre mais ça ne dépend pas de moi (eh oui, art.1: le chef à toujours raison! art. 2: si le chef a tort se reporter à l'article 1).
    si jamais, je peux t'envoyer mon source par mail?
    j'essaie déjà ici mais ça risque d'être lourd
    @++

  • signaler à un administrateur
    Commentaire de hfr11 le 26/10/2007 14:20:22

    Bonjour à tous,
    Bravo  et merci...
    Ne serait-il pas utile d'y ajouter quelques explications sur le "comment faire" pour qu'un thread Ti fasse une pause ou attende tout simplement que le thread Tj lui signale un événement ?
    L'utilisation de la méthode WaitFor notamment est pour moi très confuse, aucun exemple n'est fourni. A quel endroit fait-on appel à WaitFor, depuis quel thread et avec quel paramètre ?
    Ex : Thd1 écrit dans Tableau et Thd2 lit dans Tableau et j'aimerais que Thd2 fasse une pause pendant que Thd1 écrit et que Thd1 attende à chaque fois que Thd2 ait fini de lire avant de commencer à écrire, etc...
    Merci beaucoup, Patrice

  • signaler à un administrateur
    Commentaire de mounjetado le 26/10/2007 14:41:24

    hfr11 va voir sur ce lien:
    http://www.delphifr.com/code.aspx?ID=42613
    pour l'avoir essayé et adopté, je remercie encore florenth.
    je pense que c'est ce que tu recherches...
    Bernard

  • signaler à un administrateur
    Commentaire de istark le 14/05/2008 11:34:01

    Bonjour
    En parlant de thread, j'ai repris un projet deja réalisé mais qui consomme enormement de ressources, j'ai pensé que c'est peut etre du au grand nombre de thread utilisé. Par contre, je voudrais savoir si on peut reduire ce nombre et sur quoi on se repose pour ca?!!
    Ci quelqu'un a une idée, ca serais tres gentille de me la faire parvenir
    Merci bcp davance

  • signaler à un administrateur
    Commentaire de Forman le 14/05/2008 11:43:50

    istark: je crois qu'il faudrait en dire plus sur le type de projet dont tu parles pour qu'on puisse te dire qqchose d'utile... Selon les cas, un grand nombre de threads peut être ou non souhaitable.

    En outre (sauf cas de programmation "maladroite") le fait d'augmenter le nombre de threads ne devrait pas (trop) augmenter la consommation de resources d'un prog. Par contre s'il y a vraiment trop de threads actifs en même temps (plus de 16 je crois) ça peut affecter les performances de tout le système. Ceci dit, sauf cas particuliers, les threads passent la plupart de leur temps dans un état de veille (à attendre qu'un événement/sémaphore/section critique etc... soit signalé). Dans ce cas-là ils ne consomment aucune resource.

Ajouter un commentaire

Pub



Appels d'offres

CalendriCode

Septembre 2008
LMMJVSD
1234567
891011121314
15161718192021
22232425262728
2930     

VS Express FR Gratuit !

VS Express en français et 100% gratuit !

Boutique

Boutique de goodies CodeS-SourceS