0% ont trouvé ce document utile (0 vote)
18 vues18 pages

Gestion des processus sous Windows

Ce document décrit les processus et les threads sous Windows. Il explique ce qu'est un processus, comment en créer à l'aide de ShellExecute et CreateProcess, et comment gérer leur fin. Il décrit ensuite les threads, leur fonctionnement et leur utilité pour la programmation multitâche.

Transféré par

Korentin Dergen
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
18 vues18 pages

Gestion des processus sous Windows

Ce document décrit les processus et les threads sous Windows. Il explique ce qu'est un processus, comment en créer à l'aide de ShellExecute et CreateProcess, et comment gérer leur fin. Il décrit ensuite les threads, leur fonctionnement et leur utilité pour la programmation multitâche.

Transféré par

Korentin Dergen
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Processus et Threads

Les processus
Un processus désigne une application en cours d’exécution. Il y a autant de processus actifs que d’application en cours
d’exécution. Et si on lance trois fois de suite une même application, Windows créée trois processus. Pour ceux qui ont
connu la programmation sous Windows 3.x, la notion de processus correspond à la notion d’instance..
Pour Windows, les processus s’exécutent dans des espaces mémoires indépendants. Un processus ne peut accéder à la zone
mémoire d’un autre processus, même si il s’agit de deux processus d’une même application.
Un processus sous Windows est caractérisé par deux valeurs : un Handle (de type HANDLE) et un identifiant(de type
DWORD). Le Handle permet à Windows d’envoyer des messages au processus (par exemple les messages de rafraîchissement
de l’affichage, les messages de la souris….). L’identifiant permet à Windows d’atteindre un processus (par exemple pour
fermer un processus récalcitrant par l’intermédiaire du gestionnaire de tâches.)

La création d’un processus


La question qui revient le plus souvent chez les débutants, c’est « Comment lancer une application à partir de Delphi ? ».
On pourrait la traduire par : « Comment créer de nouveaux processus à partir de Delphi ? ». Invariablement, la réponse
qui revient est « Il faut utiliser l’API Windows ShellExecute ». ShellExecute est l’équivalent du menu « Démarrer-
>Exécuter ». Si on lui donne un nom de document elle va chercher dans la liste des associations l’application qui sert à
ouvrir ce dcument.

La déclaration (traduite du SDK de Windows) .de cette fonction est :


function ShellExecute(hwnd : HWND, lpOperation : PChar, lpFile PChar, lpParameters : PChar,
lpDirectory : PChar, nShowCmd : integer) : HINSTANCE;

HWND équivaut au type integer et HINSTANCE équivaut au type LongWord.


hwnd est le handle de fenêtre dans lequel le processus va s’afficher. Si la valeur est nil, alors le processus va créer son
propre handle de fenêtre
lpOperation peut prendre trois valeurs :
§ ‘open’ : ouvre le fichier spécifié par lpFile (si c’est un document, ouvre l’application associée)
§ ‘print’ : imprime le fichier spécifié par lpFile. Si le fichier ne peut être imprimé (un exécutable par exemple), réagis
comme si lpOperation avait la valeur ‘open’.
§ ‘explore’ : ouvre une fenêtre d’explorateur sur le répertoire spécifié dans lpFile.

lpFile est le nom du document ou de l’exécutable à ouvrir.


lpParameters est utilisé dans le cas où lpFile est un exécutable, pour passer la liste des paramètres. Peut être égal à nil.
lpDirectory est le répertoire par défaut pour rechercher lpFile (si lpFile n’est pas le chemin complet). Peut être égal à
nil
nShowCommand est un drapeau spécifiant l’état d’ouverture de la fenêtre. Il peut prendre les valuers suivantes :
§ SW_HIDE : cache la fenêtre du processus et active une autre fenêtre
§ SW_MAXIMIZE : Affiche la fenêtre du processus dans le mode plein écran, sans forcément lui donner le focus
§ SW_MINIMIZE : Affiche la fenêtre du processus dans le mode réduit et donne le focus à la fenêtre suivante
§ SW_RESTORE : Affiche la fenêtre du processus dans son état précédant
§ SW_SHOW : Affiche la fenêtre dans son état précédant et lui donne le focus.
§ SW_SHOWDEFAULT : Affiche la fenêtre du processus dans l’état définit par ses paramètres d’initialisation.
§ SW_SHOWMAXIMIZED : Correspond à SW_MAXIMIZE, plus donne le focus à la fenêtre du processus

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 1
§ SW_SHOWMINIMIZED : Correspond à SW_MINIMIZE, plus donne le focus à la fenêtre du processus
§ SW_SHOWMINNOACTIVE : Correspond à SW_MINIMIZE, mais laisse le focus sur la fenêtre le possédant
auparavant
§ SW_SHOWNA : correspond à SW_SHOW, mais laisse le focus sur la fenêtre le possédant auparavant
§ SW_SHOWNORMAL : Correspond à SW_SHOWDEFAULT, plus donne le focus à la fenêtre du processus
Peut être égal à nil
Pour ouvrir un programme dans son état par défaut en lui donnant le focus, on fera donc :
ShellExecute (nil,’c:\MonSoft\[Link]’,nil,nil,SW_SHOWNORMAL) ;

Cependant, si ShellExecute permet de créer un processus, un fois que le processus est créé, on ne peut plus intervenir
dessus. Par exemple il peut être utile de guetter la fin du processus. Pour un usage plus précis des processus, il est
préférable d’utiliser la fonction CreateProcess.
La déclaration de CreateProcess est :
function CreateProcess(lpApplicationName : PChar, lpCommandeline : PChar, lpProcessAttributes :
LPSECURITY_ATTRIBUTES, lpThreadAttributes : LPSECURITY_ATTRIBUTES, bInheritHandles : boolean,
dwCreationsFlags : DWORD, lpEnvironnement : Pointer, lpCurrentDirectory : PChar, lpStartUpInfo :
STARTUPINFO, lpProcessInformation : PROCESS_INFORMATION) : Boolean

lpApplicationName est le nom de l’application à ouvrir. Si l’application ne se trouve pas dans le répertoire de Windows,
ou dans les répertoires donnés par les variablement d’environnement, il faut préciser le chemin complet.
lpCommandline correspond à la liste de paramètre à fournir au programme. Si lpApplicationName est égal à nil, alors le
premier paramètre est pris comme nom d’application.
lpProcessAttributes est la définition des paramètres de sécurité pour le nouveau processus. En laissant lpProcessAttibutes
à nil, le processus récupère les paramètres de sécurité par défaut.
lpThreadAttributes est identique à lpProcessAttributes mais s’applique pour le cas du thread principal. Voir le paragraphe
sur les threads ci dessous.
bInheritHandles indique si le processus enfant peut utiliser les handles de ses parent
dwCreationFlags est un drapeau qui indique le mode de création du processus enfant.
lpEnvironnement est un pointeur vers les paramètres d’environnement du nouveaux processus (PATH…). Si
lpEnvironnement est à nil, alors le processus enfant récupère les variables d’environnement du processus père.
lpCurrentDirectory : chemin du répertoire courant du nouveau processus. Si lpCurrentDirectory est à nil, alors le processus
enfant récupère le répertoire courant du processus père.
lpStartInfo : est un record qui défini le mode d’affichage de la fenêtre du nouveau processus.
lpProcessInfo est un record qui est renseigné par l’appel à la fonction CreateProcess. C’est par lpProcessInfo que l’on
récupère les information concernant le Handle et l’ID du processus un fois qu’il est crée.
Pour créer un processus on fait donc :

var
Si : STARTUPINFO;
Pi : PROCESS_INFORMATION;

begin
ZeroMemory(@si,sizeof(STARTUPINFO));
[Link]:=STARTF_USESHOWWINDOW;
[Link]:=SW_SHOWNORMAL;
CreateProcess(nil,'[Link]',nil,nil,True,0,nil,nil,Si,Pi);
end ;

La fonction ZeroMemory sert à remplir Si avec des 0.


Pour attendre la fin d’un processus on fait :

WaitForSingleObject([Link],INFINITE);
MessageBox(handle,'c''est fini','Fin',MB_OK);

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 2
La boite de dialogue n’apparaîtra que lorsque NotePad sera fermé
Pour forcer la fermeture de NotePad on fait :
TerminateProcess([Link],0)

Pour plus de détails, voir l’application Process sur le CD-ROM

Les threads
Windows est un système multitâche. Cela signifie qu’il peut exécuter plusieurs actions en même temps. Par exemple il
n’est pas rare de travailler sur un tableur pendant que le logiciel de messagerie interroge à intervalles réguliers le contenu
de la boite e-mail, en même temps qu’un antivirus surveille l’activité de toutes les applications en cours de fonctionnement.
Dans le paragraphe précédent, il a été question de processus. En développant l’analyse, on pourrait penser que un processus
égal une tâche. En fait il n’en est rien. Un processus contient au minimum une tâche, mais il peut très bien en contenir
plusieurs. Implicitement, lorsqu’on démarre un processus, on crée en même temps la tâche principale de ce processus. On
peut en déduire qu’un processus est une sorte de conteneur de tâches. Les tâches sont communément appelées « thread »
dans la terminologie Windows. Et lorsqu’on parle de multitâche, on emploiera le terme de « multi-threading ».

Principe de fonctionnement des threads


Un thread principal est crée en même temps que chaque processus.. A partir de ce thread principal, on peut créer un
ou plusieurs threads supplémentaires. Chaque thread est identifié par un Handle et un ID. Tous les threads d’un même
processus partagent les même ressources, les même fichiers ouverts, les même variables globales. Par contre chaque thread
a sa propre pile. On peut considérer que tout code qui s’exécute au sein d’un thread est en « vase clos ». Bien sur les threads
sont indépendants les uns des autres et c’est même leur principal utilité. . Pour donner l’impression de simultanéité des
threads, Windows va en fait accorder un certains nombres de cycles par thread, puis passer au suivant. Toutefois il existe
une notion de niveau de priorité, définie par le programmeur. Par exemple on peut décider qu’un thread a une priorité faible
car il demande beaucoup de ressources, et que pendant ce temps là l’utilisateur ne peut rien faire d’autre. Un thread peut
être momentanément suspendu (comme un magnétoscope mis en pause).

Pourquoi utiliser des threads ?


En règle générale on a besoin de faire une application multitâche lorsque l’un des traitements effectué est long, et surtout
bloquant. Par exemple, dans le cas d’une gestion commerciale : si un calcul statistique sur de milliers d’enregistrements
demande de longue minutes, l’utilisateur peu avoir envie de consulter la fiche d’un client qui lui téléphone.. Il serait
raisonnable de mettre le cacul dans une tâche de fond moins prioritaire que la consultation des fiches.. L’utilisateur sera
certainement plus d’accord de perdre quelques minutes plutôt que de perdre un client.

Les threads dans Delphi


La documentation du kit de développement standard de Windows (le SDK !) est relativement lourde et obscure sur
l’utilisation des threads. Pour simplifier le travail des développeurs, Borland a créer une classe de base : TThread qu’il
suffit d’hériter. Gérer les threads devient aussi facile que de gérer les différentes fiches.
Pour créer un nouveau thread dans un projet, il suffit d’aller dans le menu : Fichier->Nouveau->Autres et de chercher
parmi les assistants disponibles celui intitulé « Objet Thread ». Une boîte de dialogue s’ouvre alors pour demander le nom
de l’objet, puis une nouvelle unité est crée, portant le squelette d’une class dérivée de TThread. Ce qui signifie qu’il y aura
une unité crée pour chaque thread. Voici ce que l’on peut lire sur le squelette de la classe :

unit UnitMyThread_1;

interface

uses
Classes;

type
MyThread_1 = class(TThread)
private
{ Déclarations privées }

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 3
protected
procedure Execute; override;
end;

implementation

{ Important : les méthodes et propriétés des objets de la VCL ou CLX peuvent uniquement être
utilisées dans une méthode appelée avec Synchronize. Par exemple,

Synchronize(UpdateCaption);

UpdateCaption ayant l''apparence suivante :

procedure MyThread_1.UpdateCaption;
begin
[Link] := 'Updated in a thread';
end; }

{ MyThread_1 }

procedure MyThread_1.Execute;
begin
{ Placez le code du thread ici }
end;
end.

Le code que l’on souhaite donc exécuter au sein du thread sera donc placé (ou tout du moins appelé) dans la méthode
Execute de la classe.
La classe ancêtre TThread fournit les propriétés de bases :
property FreeOnTerminate : Boolean

Si FreeOnTerminate vaut True, alors l’objet thread est détruit lorsqu’il a terminé son exécution. Sinon on doit libérer le
thread en appelant la méthode Free.

property Handle : integer ;

Contient le handle du Thread ;

property Priority : TThreadPriority;

Indique le niveau de priorité du thread. Priority peu prendre les valeurs suivantes :
§ tpIdle : le niveau de priorité le plus bas. Le thread guette le moment où Windows ne fait rien.
§ tpLowest : contrairement à ce que son nom indique, ce n’est pas le niveau de priorité le plus bas, mais seulement le plus
bas parmi les threads systématiquement exécutés (contrairement à tpIdle).
§ tpLower : priorité légèrement plus faible que la normale.
§ tpNormal : priorité standard.
§ tpHigher : priorité légèrement plus élevée que la normale
§ tpHighest : priorité la plus élevée ne bloquant les autres threads
§ tpTimeCritical : priorité la plus élevée, pouvant bloquer les autres threads.

property Suspended: Boolean;

Indique si l’exécution du thread est stoppée.

property Terminated: Boolean;


Indique si le thread s’est éxécuté dans son intégralité.

Le constructeur hérité de la classe TThread est le suivant :


constructor Create(CreateSuspended: Boolean);

Si CreateSuspended vaut True, alors le thread est initialisé mais sont éxécution est stoppée. Sinon le thread s’exécute dès
qu’il est créé. Pour redémarré un thread qui est suspendu, il suffit de passer sa propriété Suspended à False
Auteur : Laurent Berne Mise en page : Céline Cantat
[Link]@[Link] [Link]@[Link] 4
Pour commencer il faut donc définir la procédure Execute. On peut commencer avec une simple boîte de dialogue « Hello
Word » :

procedure MyThread_1.Execute;
begin
MessageBox(0,’Hello Word !’, ‘Bonjour du Thread’, MB_OK);
end;

Ne pas oublier de rajouter ‘Windows’ dans la clause Uses

Maintenant il faut utiliser le thread dans le projet. Dans la fiche qui déclenche le thread, il faut ajouter l’unité qui contient
le thread dans la clause Uses. Ensuite, on créer un objet thread en appelant son constructeur

procedure TForm1.Button1Click(Sender: TObject);


var
MyThread_1 : TMyThread_2;
begin

MyThread_1:=TMyThread_2.Create(False);
end;

Lors de l’exécution de la procédure Button1Click, une boîte de dialogue apparaît. Bien sur ce petit exemple n’a pas
vraiment d’utilité. Son seul but est de montrer l’insertion d’un thread dans le code.
Pour aller plus loin dans l’étude des threads, nous allons étudier l’implémentation d’un compteur dans un thread . Le
principe : afficher dans un label le contenu d’une variable qui qui s’incrémente toutes les secondes. Par principe, les
threads sont des espaces clos. Il faut donc lui transmettre le TLabel dans lequel le compteur va s’afficher. Dans ce but, il
faut faire une surcharge du constructeur :
Voici donc la déclaration du nouvel objet thread : MyThread_2 , créé par la méthode décrite ci dessus..:

unit UnitMyThread_2;

interface

uses
Classes,StdCtrls,SysUtils,Windows;

type
TMyThread_2 = class(TThread)
private
{ Déclarations privées }
Display : TLabel;
protected
procedure Execute; override;
public
constructor Create(ADisplay : TLabel);
end;

L’implémentation du nouveau constructeur :

constructor TMyThread_2.Create(ADisplay: TLabel);


begin
FreeOnTerminate := True;
inherited Create(False);
Display :=ADisplay;
end;

Dans cette implémentation on distingue l’appelle au constructeur originel hérité de la classe TThread par :
inherited Create(False);

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 5
La variable privée Display est initialisée pendant la création de l’objet. Cette variable est en fait le TLabel qui va
afficher le contenu du compteur.

L’implémentation du traitement effectué dans le thread :

procedure TMyThread_2.Execute;
var
Count : integer;
begin
{ Placez le code du thread ici }
Count :=0;
Repeat
Sleep(1000);
Inc(Count);
[Link]:= IntToStr(Count);
until Count >30;

end;

Dans le projet, sur la forme principal il faut placer un objet TLabel. Pour activer le thread on procède ainsi :

procedure TForm1.Button2Click(Sender: TObject);


var
MyThread_2 : TMyThread_2;
begin

MyThread_2:=TMyThread_2.Create(Label1);

end;

A l’exécution, le contenu du label est modifié toutes les secondes pour afficher le résultat du compteur. Pour avoir un
aperçu du principe du mutlti-threading, il faut rajouter un deuxième TLabel sur la fiche. Ensuite il faut créer un deuxième
objet thread et l’activer indépendamment du premier :

procedure TForm1.Button3Click(Sender: TObject);


var
MyThread_2b : TMyThread_2;
begin

MyThread_2b:=TMyThread_2.Create(Label2);

end;

A l’exécution, les deux threads sont crées indépendamment par l’utilisateur. L’affichage renvoyé indique bien que les deux
threads s’exécutent de manière totalement indépendante.

L’utilisation de fiches au sein d’un thread


Lors de l’exécution de traitements particulièrement longs, il est courant d’informer l’utilisateur de la progression du
calcul.
Pour le design de la fiche, il faut procéder comme d’habitude. Sur une fiche vide, on pose un composant TProgressBar
ainsi qu’un TLabel. Le composant TLabel est destiné à mettre le message destiné à l’utilisateur. Dans les propriétés
BorderIcons, on desactive tous les choix possible afin d’empêcher l’utilisateur de fermer la fiche. Dans les options du
projet, on retire la fiche de la liste des fiches crées automatiquement. C’est le thread qui sera chargé de créer et de détruire
la fiche. Cette fiche est appelée : ProgressForm (de class TProgressForm)
La fiche ressemble à peu près à ceci :

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 6
Figure 19.1 : la fiche complétée.

Dans l’unité qui contien le thread, il faut bien sûr rajouter dans la clause uses l’unité contenant la fiche. Dans l’objet thread,
on rajoute une variable privée donnant accés au contenu de la fiche. La déclaration du thread devient donc :

unit UnitMyThread_3;

interface

uses
Classes,SysUtils,UnitProgressForm;

type
TMyThread_3 = class(TThread)
private
{ Déclarations privées }
FProgressForm : FTProgressForm;

protected
procedure Execute; override;
public
constructor Create(Suspended : Boolean);

end;

Le constructeur est modifié pour initialiser la fiche :

constructor TMyThread_3.Create(Suspended: Boolean);


begin
FreeOnTerminate := True;
inherited Create(Suspended);

FProgressForm := [Link](nil);
[Link];
...OnTerminate := OnTerminateProcedure;

end;

Dans la procedure Execute du thread :

procedure TMyThread_3.Execute;
var
Count : integer;
begin
Count :=0;
Repeat

Sleep(100);
Inc(Count);
if Assigned(FProgressForm) then
[Link]:=Count;

until Count >100;


end;

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 7
Enfin la procedure OnTerminateProcedure.

procedure TMyThread_3.OnTerminateProcedure(Sender: TObject);


begin
if Assigned(FProgressForm) then
[Link];
end;

Chaque fois que l’on accède à la fiche, on teste si elle est correctement initialisée avec la fonction Assign. A la fin du
thread, on libère la fiche avec l’appel à [Link]. La procédure OnTerminateProcedure est
appelée juste avant la destruction de l’objet car dans le constructeur elle a été affecté à l’évenement OnTerminate par
la ligne :
OnTerminate := OnTerminateProcedure;

Tester si un thread est terminé


La procédure la plus simple consiste à rajouter une variable publique et à la modifier durant l’évenement OnTerminate. Par
exemple en modifiant l’exemple précédent :
unit UnitMyThread_4;

interface

uses
Classes,SysUtils;

type
TMyThread_4 = class(TThread)
private
{ Déclarations privées }
FProgressForm : FTProgressForm;
procedure OnTerminateProcedure(Sender : TObject);
protected
procedure Execute; override;
public
ThreadTerminated : Boolean;
constructor Create(Suspended : Boolean);
end;

On initialise la variable dans le constructeur


constructor TMyThread_4.Create(Suspended: Boolean);
begin
FreeOnTerminate := True;
inherited Create(Suspended);
FProgressForm := [Link](nil);
[Link];
OnTerminate := OnTerminateProcedure;
ThreadTerminated := False;
end;

Enfin dans la procedure OnTerminateProcedure :


procedure TMyThread_4.OnTerminateProcedure(Sender: TObject);
begin
if Assigned(FProgressForm) then
[Link];
ThreadTerminated:=True;
end;

Enfin dans le projet :

procedure TForm1.Button5Click(Sender: TObject);

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 8
var
MyThread_4 : TMyThread_4;
begin
MyThread_4 := TMyThread_4.Create(False);

Repeat
// boucle vide pour attendre la fin du thread
[Link];
until MyThread_4.ThreadTerminated;
MessageBox(Handle,'Le thread est terminé','Avertissement',MB_OK);
end;

La boucle Repeat .. Until va tester en permanence la variable ThreadTerminated qui a été rajoutée. A
l’exécution, la boîte de dialogue apparaîtra lorsque le thread aura terminé son éxécution

Suspendre un thread en cours d’utilisation


Pour suspendre un thread momentanément et puis le réactiver, on peut modifier sa propriété Suspend. Par exemple en
reprenant l’exemple précédent :

procedure TForm1.Button6Click(Sender: TObject);


var
MyThread_4 : TMyThread_4;
begin
MyThread_4 := TMyThread_4.Create(False);

Repeat
[Link];
MyThread_4.Suspended := [Link];
until MyThread_4.ThreadTerminated;
MessageBox(Handle,'Le thread est terminé','Avertissement',MB_OK);
end;

A l’exécution, le thread se mettra en pause à chaque fois que l’on cochera la case à cocher, et se remettra en route dès
qu’on la décochera.

Définir la priorité du thread


Pour illustrer l’intérêt des priorités, il faut concevoir un thread gourmand en ressources. On modifie donc la procédure du
thread précédent :
La nouvelle déclaration :

unit UnitMyThread_5;

interface

uses
Classes,SysUtils,UnitProgressForm, Windows;

type
TMyThread_5 = class(TThread)
private
{ Déclarations privées }
FProgressForm : TProgressForm;
FName : string;
procedure OnTerminateProcedure(Sender : TObject);

protected
procedure Execute; override;
public
ThreadTerminated : Boolean;

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 9
constructor Create(Suspended : Boolean; Name : string);
end;

Le constructeur a été modifié, afin de pouvoir attribuer un nom au thread.

constructor TMyThread_5.Create(Suspended: Boolean; Name : string);


begin
FreeOnTerminate := True;
inherited Create(Suspended);
FProgressForm := [Link](nil);
[Link];
OnTerminate := OnTerminateProcedure;
ThreadTerminated := False;
FName :=Name;

end;

La nouvel procédure Execute :

procedure TMyThread_5.Execute;
var
Count : integer;
begin
Count :=0;
Repeat
Inc(Count);
if Assigned(FProgressForm) then
[Link]:=Count div 1000;

until Count >100000;

end;

La procédure Execute ne fait pas appel à la fonction Sleep. Elle utilise donc tout le temps de calcul dont elle dispose
Enfin la nouvelle procédure OnTerminateProcedure :

procedure TMyThread_5.OnTerminateProcedure(Sender: TObject);


var
ThisMessage : string;
begin
if Assigned(FProgressForm) then
[Link];
ThreadTerminated:=True;
ThisMessage := 'Le thread '+ FName + ' est terminé !';
MessageBox(0,PChar(ThisMessage),'Avertissement',MB_OK);
end;

Lorsque le thread est terminé, une boite de dialogue va s’ouvrir pour indiquer que le nom du thread qui se termine.

Dans le projet :

procedure TForm1.Button7Click(Sender: TObject);


var
MyThread_Faible : TMyThread_5;
MyThread_Prioritaire : TMyThread_5;
begin
MyThread_Faible := TMyThread_5.Create(False,'Faible');
MyThread_Faible.Priority:= tpIdle ;
Sleep(500);
MyThread_Prioritaire := TMyThread_5.Create(False,'Prioritaire');
MyThread_Prioritaire.Priority := tpTimeCritical ;

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 10
end;

Durant l’exécution , le thread MyThread_Faible est lancé avant le thread MyThread_Prioritaire. Pourtant
MyThread_Prioritaire se termine avant MyThread_Faible.

Synchronisation de deux threads.


Comme expliqué précédemment, les threads sont exécutés en milieu « clos », sans contact avec l’extérieur. Pourtant les
développeurs ont parfois le besoin que deux (ou plus) threads s’éxecutent parallèlement, se partage une même ressource
(par exemple l’accès à un lecteur physique, où à un fichier..). Il est même parfois nécessaire que ces deux threads soient
issus de processus différents (donc d’applications différentes). Afin d’éviter les conflits lors de ces partages, Windows met
à la disposition des développeurs trois objets de synchonisation : les mutex, les sémaphores et les évenements.

Les Mutex
Ce sont des objets de synchronisation que se partage les threads en cours d’exécution. Leur nom vient de la contraction
de « MUTual EXclusive objects ». Pour faire une analogie simple : un mutex est le témoin que se passe les coureurs dans
une course de relais, les coureurs étant les threads. Le premier coureur démarre, emportant le témoin avec lui. A un certain
moment de sa course, le deuxième coureur démarre lui aussi mais modère son allure (voir s’arrête ??) tant que le premier
coureur ne lui a pas passer le témoin.
Dans le cas des threads, un thread démarre, bloquant l’usage du mutex. Plus tard dans le code, un deuxième thread
démarre et bloque sa propre exécution en attendant que le premier thread libère le mutex. Ce deuxième thread est au
courant de l’état du mutex en interrogeant l’API WaitForSingleObject.. Une fois le mutex libéré, le deuxième thread s’en
«empare» et poursuit son exécution.
Dans la terminologie, on dit qu’un mutex est à l’état «non signalé» lorsque un thread bloque son usage et qu’il est à l’état
«signalé» si il est libre.
Pour instancier un nouvel objet mutex, on fait appel à l’API Create Mutex :
function CreateMutex(lpMutexAttributes : LPSECURITY_ATTRIBUTES ; bInitialState : Boolean ; lpName :
PChar) : THandle ;

lpMutexAttributes est la définition des paramètres de sécurité pour le nouveau processus. En laissant lpMutexAttributes à
nil, le processus récupère les paramètres de sécurité par défaut.
bInitialState indique si le thread est signalé (True) ou non.
lpName est le nom du mutex..

Retourne le handle du mutex

Pour retrouver un mutex par son nom on utilise l’API OpenMutex :


function OpenMutex(dwDesiredAccess : LONGWORD ; bInheritHandle : Boolean ; lpName : PChar) : THandle
;

dwDesiredAccess est de type DWORD (c’est à dire LONGWORD). il peut prendre deux valeurs :
§ MUTEX_ALL_ACCESS : permet tous les accès possible à l’objet mutex
§ SYNCHRONIZE : pour Windows NT/2000/XP seulement : permet d’utiliser le Handle dans une fonction d’attente.
bInheritHandle détermine si les processus enfants peuvent hériter ou non.
lpName : Nom du mutex recherché.
Retourne le handle du mutex recherché

Pour attendre que le mutex soit disponible il faut utiliser l’API WaitForSingleObject. Cette fonction de l’API bloque

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 11
l’exécution du code jusqu’à ce que l’objet (dans notre cas un mutex, mais cela peut être d’autres objets, comme nous le
verrons plus tard..), symbolisé par son Handle, soit disponible.
function WaitForSingleObject(hObject : THandle ; dwTimeoutInterval : DWORD) : DWORD ;
hObject est le handle du mutex.
dwTimeoutInterval est le temps d’attente exprimé en millisecondes. Peut prendre aussi la valeur INFINITE (attends
indéfiniment).

Une fois que l’on a plus besoin du mutex, il faut le libérer pour que d’autres threads puissent le récupérer. On utilise l’API
ReleaseMutex :

function ReleaseMutex(hObject : THandle) ;

hObject est le handle du mutex que l’on libère.

Note : pour synchroniser un thread avec plusieurs autres simultanément, on préférera la foncion WaitForMutlipleObjects
et on associera un mutex différent pour chaque thread synchronisé.

Enfin il ne faut pas oublier de libérer le mutex une fois que tous les threads l’on utilisé. On utilise CloseHandle :
function CloseHandle(hObject : THandle) : Boolean;

hObject est le handle du mutex.

Pour appliquer tout cela, nous allons faire un thread qui monopolise le mutex le temps d’afficher une boite de dialogue.

La déclaration :

unit UnitMyThread_6;

interface

uses
Classes,Windows;

type
TMyThread_6 = class(TThread)
private
{ Déclarations privées }
FMutex : THandle;
FName : string;

protected
procedure Execute; override;
public
constructor Create(Suspended : Boolean; Name : string);
end;

Le constructeur :

constructor TMyThread_6.Create(Suspended: Boolean; Name : string);


begin
FreeOnTerminate := True;
inherited Create(Suspended);

FMutex :=OpenMutex(MUTEX_ALL_ACCESS, True, 'MyMutex');


FName := Name;

end;

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 12
La procedure Execute :

procedure TMyThread_6.Execute;
var
MyMessage : string;
begin
{ Placez le code du thread ici }
WaitForSingleObject(FMutex,INFINITE);
MyMessage := 'Le thread '+ FName +' vient de récupérer le mutex';
MessageBox(0,PChar(MyMessage),'Avertissement',MB_OK);
ReleaseMutex(FMutex);

WaitForSingleObject(FMutex,INFINITE);
MyMessage := 'Le thread '+ FName +' vient de récupérer une deuxième fois le mutex';
MessageBox(0,PChar(MyMessage),'Avertissement',MB_OK);
ReleaseMutex(FMutex);

WaitForSingleObject(FMutex,INFINITE);
MyMessage := 'Le thread '+ FName +' vient de récupérer une dernière fois le mutex';
MessageBox(0,PChar(MyMessage),'Avertissement',MB_OK);
ReleaseMutex(FMutex);

end;

Et dans le code de l’application :

procedure TForm1.Button8Click(Sender: TObject);


var
Thread_1 : TMyThread_6;
Thread_2 : TMyThread_6;
Mutex : THandle;
begin
Mutex:= CreateMutex(nil,False,'MyMutex');
Thread_1 := TMyThread_6.Create(False,'Thread_1') ;
Thread_2 := TMyThread_6.Create(False,'Thread_2') ;
CloseHandle(Mutex);

end;

Lors de l’exécution, deux instances du même thread sont appelées. Chacune d’elles capture puis relâche le mutex, à
plusieurs reprises, ce qui donne cet effet d’alternance (une fois Thread_1, une fois Thread_2..)

Les sémaphores
Le principe des sémaphores est de gérer des accès simultanés à des ressources. Par exemple on peut très bien imaginer que
trois threads essaient d’écrire dans un fichier. Si on laisse les trois threads sans synchronisation, il y aura des violations.
Les sémaphores décomptent les accés aux ressources et font patienter les threads. Les threads, lors de leur execution,
interrogent le sémaphore par l’intermédiaire de l’API WaitForSingleObject. Ils sont bloqués tant que le sémaphore ne les
autorise pas à continuer. Pour cela, le sémaphore vérifie le nombre de threads accédant à la ressource, et si il reste «de la
place disponible», envoie le signal qui débloquera les threads appelant.

Pour instancier un nouveau sémaphore, on appelle la fonciton CreateSemaphore


function CreateSemaphore(lpSemaphoreAttributes LPSECURITY_ATTRIBUTES; lInitialCount : LONGINT ;
lMaximumCount : LONGINT; lpName : PChar) : THandle;

lpSemaphoreAttributes est la définition des paramètres de sécurité pour le nouveau processus. En laissant
lpSemaphoreAttribute à nil, le processus récupère les paramètres de sécurité par défaut.
lIntialCount est le nombre de resources initialement dipsonibles (doit être supérieur à 0)
lMaximumCount est le nombre total de ressources disponibles.

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 13
lpName est le nom du sémaphore.
Retourne le handle du sémaphore crée

Pour ouvrir un sémaphore, on utilise l’API OpenSemaphore :


function OpenSemaphore(dwDesiredAccess : LONGWORD ;bInheritHandle : Boolean; lpName : PChar) :
THandle ;

dwDesiredAccess est de type DWORD (c’est à dire LONGWORD). il peut prendre deux valeurs :
§ SEMAPHORE_ALL_ACCESS : permet tous les accès possible à l’objet mutex
§ SEMAPHORE_MODIFY_STATE : autorise le décompte du sémaphore lors du relâchement.
§ SYNCHRONIZE : pour Windows NT/2000/XP seulement : permet d’utiliser le Handle dans une fonction d’attente.
bInheritHandle détermine si les processus enfants peuvent hériter ou non.
lpName : Nom du sémaphore recherché.
Retourne le handle du sémaphore recherché

Pour attendre la libération d’une ressource on utilise encore l’API WaitForSingleObject.

Pour libérer une ressource du sémaphore : on appelle la fonction ReleaseSemaphore :


function ReleaseSemaphore(hObject : THandle ; lReleaseCount : LONGINT ; lpPreviousCount : POINTER) :
Boolean

hObject est le handle du sémaphore


lReleaseCount est le nombre de ressource libérée.
lpPreviousCount est un pointeur sur un LONGINT qui pointant sur la variable contenant le nombre de ressource. Peut être
égal à nil.
Enfin quand on a plus besoin de sémaphore, on le libére avec CloseHandle.

Pour la mise en application la ressource sera symbolisée par un TProgressBar propriétaire d’une fiche externe au
thread(figure 19.2)

Figure 19.2 : La fiche complétée

Afin de simuler l’appel d’une ressources exclusives, on définit à cette fiche une méthode InvokeDisplay :

function [Link](Name : string): TProgressBar;


begin
[Link]:=Name;

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 14
Result :=ProgressBar1;
end;

La déclaration du nouveau thread :

unit UnitMyThread_7;

interface

uses
Classes,ComCtrls,SysUtils,Windows,UnitSemaphoreForm;

const SEMAPHORE_ALL_ACCESS= EVENT_ALL_ACCESS;


type

TMyThread_7 = class(TThread)
private
{ Déclarations privées }
FSemaphore : THandle;
FSemaphoreForm : TSemaphoreForm;
FName : string;
protected
procedure Execute; override;
public
ProgressBar : TProgressBar;
constructor Create(Suspended : Boolean; Name : string ; SemaphoreForm : TSemaphoreForm);
end;

On déclare la constante SEMAPHORE_ALL_ACCESS car elle n’est pas délcarée dans la source [Link]. Ainsi on
est en accord avec le SDK Win32.

Le constructeur du Thread :

constructor TMyThread_7.Create(Suspended: Boolean; Name: string; SemaphoreForm : TSemaphoreForm);


begin
FreeOnTerminate := True;
inherited Create(Suspended);
FSemaphore :=OpenSemaphore(SEMAPHORE_ALL_ACCESS , True, 'MySemaphore');
FSemaphoreForm := SemaphoreForm;
FName := Name;
ProgressBar:=nil;
end;

La procédure Execute :
procedure TMyThread_7.Execute;
var
Count : integer;
begin
Count :=0;
WaitForSingleObject(FSemaphore,INFINITE);

ProgressBar := [Link](FName);

Repeat
Inc(Count);
if Assigned(ProgressBar) then
[Link] :=Count div 1000;
until Count >100000;
ReleaseSemaphore(FSemaphore,1,nil);
end;

Et enfin dans l’application on créée trois instance de ce thread qui vont invoquer la barre de progression.

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 15
procedure TForm1.Button9Click(Sender: TObject);
var
SemaphoreForm : TSemaphoreForm ;
Thread_1 : TMyThread_7;
Thread_2 : TMyThread_7;
Thread_3 : TMyThread_7;
Semaphore : THandle;
begin
SemaphoreForm := [Link](Application);
[Link];
Semaphore := CreateSemaphore(nil,1,1,'MySemaphore');

Thread_1 := TMyThread_7.Create(False,'Thread_1',SemaphoreForm);
Thread_2 := TMyThread_7.Create(False,'Thread_2',SemaphoreForm);
Thread_3 := TMyThread_7.Create(False,'Thread_3',SemaphoreForm);

CloseHandle(Semaphore);
end;

Les Evènements
C’est le dernier objet de synchronisation disponible. Un évènement, c’est un peu comme le signal de départ lors d’une
course. Il existe deux styles d’évènement. Le premier, classique, indique que tous les threads qui attendent cet évènement
peuvent démarrer. Le deuxième lui donne le départ à un seul et unique thread. Les autres attendent que l’évènement leur
autorise de démarrer. Ca pourrait s’apparenter au départ d’une course contre la montre du cyclisme.
Lors de l’exécution, les threads attendent que l’évènement se déclenche. Ensuite, c’est le
Delphi dispose d’un objet permettant d’utiliser simplement les évenements : l’objet TEvent.
Ses propriétés sont :
property Handle: THandle;
En lecture seule, le handle de l’événement.

property LastError: Integer;


Valeur renvoyée si la création de l’évenement échoue.
Le constructeur de cet objet sert à la fois à la création ou à ouvrir un événement déjà crée :
constructor Create(EventAttributes: PSecurityAttributes; ManualReset, InitialState: Boolean; const
Name: string);

EventAttributes est la définition des paramètres de sécurité pour le nouveau processus. En laissant lpSemaphoreAttribute
à nil, le processus récupère les paramètres de sécurité par défaut.
ManualReset indique si l’événement reste actif une fois déclenché ou si il revient à l’état inactif.
IntialState : indique si l’événement est déclenché.
Name : nom de l’événement, nécessaire pour la synchronisation.
L’objet dispose de méthode permettant au thread qui le possède d’attendre l’événement (procedure WaitFor), de le
déclencher (procédure SetEvent) ou de le réinitialisé (procedure ReseEvent).

Pour la mise en application, l’événement sera déclenché par un clic sur un bouton.
on place le code suivant dans l’unité du thread :
unit UnitMyThread_8;

interface

uses
Classes,SyncObjs,Windows;

type
TMyThread_8 = class(TThread)
private
{ Déclarations privées }
FEvent : TEvent;
FName : string;
protected
procedure Execute; override;
Auteur : Laurent Berne Mise en page : Céline Cantat
[Link]@[Link] [Link]@[Link] 16
public
constructor Create(Suspended : Boolean; Name : string);
end;

implementation

constructor TMyThread_8.Create(Suspended: Boolean; Name: string);


begin
FreeOnTerminate := True;
inherited Create(Suspended);

FEvent :=[Link](nil,False,False,'MyEvent');
FName := Name;
end;

procedure TMyThread_8.Execute;
var
MyMessage : string;
begin
{ Placez le code du thread ici }
[Link](INFINITE);
MyMessage := 'Le thread '+ FName +' vient d''être déclenché';
MessageBox(0,PChar(MyMessage),'Avertissement',MB_OK);
end;

end.

Dans l’application on créée trois instances du thread :


procedure TForm1.Button10Click(Sender: TObject);
var

Thread_1 : TMyThread_8;
Thread_2 : TMyThread_8;
Thread_3 : TMyThread_8;
begin
Thread_1 := TMyThread_8.Create(False,'Thread_1');
Thread_2 := TMyThread_8.Create(False,'Thread_2');
Thread_3 := TMyThread_8.Create(False,'Thread_3');
end;

Enfin, le code qui déclenche l’évenement :

procedure TForm1.Button11Click(Sender: TObject);


var
Event : TEvent;
begin
Event := [Link](nil,False,False,'MyEvent') ;
[Link];
end;

Lors de l’éxecution, on créée les trois threads ; qui vont attendre l’évènement. Comme l’événement se réinitialise
automatiquement, seul un thread à la fois va se poursuivre.

Les sections critiques


Les sections critiques ne sont pas à proprement parler des objets de synchronisation. C’est une sorte de signal qui bloque
tous les threads rattachés à un même processus. Mais si d’autres threads dépendants d’autres processus veulent accéder à
la ressource, alors il y a risque de violation. Le principe de fonctionnement est des plus simples. Lors de son execution,
le thread annonce aux autres threads du processsus qu’il entre dans une section critique. Automatiquement, l’exécution
des autres threads rattachés au processus sont bloqués. Ils seront libérés lorsque le thread annoncera qu’il est sorti de la
section critique.
Delphi propose un objet TCriticalSection pour simplifier l’usage des sections critiques. Cet objet n’a pas de propriété on
l’utilise de la manière suivante :

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 17
Uses SyncObjs ;

procedure TThread_9.OnExecute ;
var
CriticalSection : TCriticalSection;
begin
CriticalSection :=[Link]; // Initialisation
[Link]; // entrée dans la section critique
…//Code à exécuter de manière critique
[Link]; // Sortie de la section critique
[Link]; // libération de l’objet
end ;

Auteur : Laurent Berne Mise en page : Céline Cantat


[Link]@[Link] [Link]@[Link] 18

Vous aimerez peut-être aussi