Classe de thread dans Delphi
Raptor[Studio Mental]
http://mental.mentsu.com
Partie 2
Le premier est le constructeur :
constructeur TThread.Create(CreateSuspended: Boolean);
commencer
hérité Créer;
Ajouter un fil ;
FSuspended := CreateSuspended ;
FCreateSuspended := CreateSuspended;
FHandle := BeginThread(nil, 0, @ThreadPRoc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
si FHandle = 0 alors
raise EThread.CreateResFmt (@SThreadCreateError, [SysErrorMessage(GetLastError)]);
fin;
Bien que ce constructeur n'ait pas beaucoup de code, il peut être considéré comme le membre le plus important car le thread est créé ici.
Après avoir appelé TObject.Create via Inherited, la première phrase consiste à appeler un processus : AddThread, et son code source est le suivant :
procédure AddThread ;
commencer
InterlockedIncrement (ThreadCount);
fin;
Il existe également un RemoveThread correspondant :
procédure RemoveThread ;
commencer
InterlockedDecrement(ThreadCount);
fin;
Leur fonction est très simple : compter le nombre de threads dans le processus en augmentant ou en diminuant une variable globale. C'est juste que le processus Inc/Dec couramment utilisé n'est pas utilisé pour augmenter ou diminuer les variables ici, mais la paire de processus InterlockedIncrement/InterlockedDecrement est utilisée. Ils implémentent exactement la même fonction, en ajoutant ou en soustrayant une à la variable. Mais ils ont une plus grande différence : InterlockedIncrement/InterlockedDecrement est thread-safe. Autrement dit, ils peuvent garantir des résultats d'exécution corrects en multithreading, mais Inc/Dec ne le peut pas. Ou, en termes de théorie du système d'exploitation, il s'agit d'une paire d'opérations « primitives ».
Prenons plus un comme exemple pour illustrer la différence dans les détails de mise en œuvre entre les deux :
D'une manière générale, l'opération d'ajout d'un aux données en mémoire comporte trois étapes après décomposition :
1. Lire les données de la mémoire
2. Données plus un
3. Stocker en mémoire
Supposons maintenant une situation qui peut se produire lors de l'utilisation de Inc pour effectuer une opération d'incrémentation dans une application à deux threads :
1. Le thread A lit les données de la mémoire (supposé être 3)
2. Le thread B lit les données de la mémoire (également 3)
3. Le fil A en ajoute un aux données (maintenant c'est 4)
4. Le thread B en ajoute un aux données (maintenant c'est aussi 4)
5. Le thread A stocke les données dans la mémoire (les données dans la mémoire sont maintenant 4)
6. Le thread B stocke également les données dans la mémoire (les données dans la mémoire sont toujours au nombre de 4, mais les deux threads en ont ajouté une, qui devrait être 5, il y a donc un résultat incorrect ici)
Il n'y a pas de problème de ce type avec le processus InterlockIncrement, car ce que l'on appelle la « primitive » est une opération ininterrompue, c'est-à-dire que le système d'exploitation peut garantir que le changement de thread ne se produira pas avant l'exécution d'une « primitive ». Ainsi, dans l'exemple ci-dessus, ce n'est qu'une fois que le thread A a fini d'exécuter et de stocker les données en mémoire que le thread B peut commencer à récupérer le numéro et en ajouter un. Cela garantit que même dans une situation multithread, le résultat sera le même. correct.
L'exemple précédent illustre également une situation de « conflit d'accès aux threads », c'est pourquoi les threads doivent être « synchronisés » (Synchroniser). Cela sera discuté en détail plus tard lorsque la synchronisation sera mentionnée.
En parlant de synchronisation, il y a une digression : Li Ming, professeur à l'Université de Waterloo au Canada, a un jour soulevé des objections à ce que le mot Synchronize soit traduit par « synchronisation » dans « synchronisation des threads ». Personnellement, je pense que ce qu'il a dit est en fait très. raisonnable. « Synchronisation » en chinois signifie « se produire en même temps », et le but de la « synchronisation des threads » est d'éviter que cela « se produise en même temps ». En anglais, Synchronize a deux significations : l'une est la synchronisation au sens traditionnel (se produire en même temps) et l'autre est "To Operate in unison" (To Operate in unison). Le mot Synchroniser dans « synchronisation des threads » doit faire référence à ce dernier sens, c'est-à-dire « garantir que plusieurs threads maintiennent la coordination et évitent les erreurs lors de l'accès aux mêmes données ». Cependant, il existe encore de nombreux mots mal traduits comme celui-ci dans l'industrie informatique, car c'est devenu une convention, je continuerai à l'utiliser ici, car le développement de logiciels est un travail méticuleux et ce qui doit être clarifié. ne doit jamais l'être. Je ne peux pas être vague.
Revenons au constructeur de TThread. La chose la plus importante est ensuite cette phrase :
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
La fonction Delphi RTL BeginThread mentionnée précédemment est utilisée ici. Elle comporte de nombreux paramètres, les principaux étant les troisième et quatrième paramètres. Le troisième paramètre est la fonction du thread mentionnée précédemment, c'est-à-dire la partie du code exécutée dans le thread. Le quatrième paramètre est le paramètre passé à la fonction thread, ici il s'agit de l'objet thread créé (ie Self). Parmi les autres paramètres, le cinquième est utilisé pour que le thread soit suspendu après la création et ne s'exécute pas immédiatement (le travail de démarrage du thread est déterminé en fonction de l'indicateur CreateSuspended dans AfterConstruction), et le sixième est de renvoyer l'ID du thread.
Examinons maintenant le cœur de TThread : la fonction de thread ThreadProc. Ce qui est intéressant, c'est que le noyau de cette classe de thread n'est pas un membre du thread, mais une fonction globale (car la convention des paramètres du processus BeginThread ne peut utiliser que des fonctions globales). Voici son code :
fonction ThreadProc(Thread : TThread) : Entier ;
var
FreeThread : booléen ;
commencer
essayer
sinon Thread.Terminé alors
essayer
Thread.Execute;
sauf
Thread.FFatalException := AcquireExceptionObject;
fin;
enfin
FreeThread := Thread.FFreeOnTerminate;
Résultat := Thread.FReturnValue;
Thread.DoTerminate;
Thread.FFinished := Vrai ;
SignalSyncEvent ;
si FreeThread alors Thread.Free ;
FinThread(Résultat);
fin;
fin;
Bien qu'il n'y ait pas beaucoup de code, c'est la partie la plus importante de l'ensemble du TThread, car ce code est le code qui est réellement exécuté dans le thread. Voici une description ligne par ligne du code :
Tout d'abord, déterminez l'indicateur Terended de la classe de thread. S'il n'est pas marqué comme terminé, appelez la méthode Execute de la classe de thread pour exécuter le code du thread. Parce que TThread est une classe abstraite et que la méthode Execute est essentiellement une méthode abstraite. exécute le code Execute dans la classe dérivée.
Par conséquent, Execute est la fonction de thread dans la classe thread. Tout le code dans Execute doit être considéré comme du code de thread, par exemple pour éviter les conflits d'accès.
Si une exception se produit dans Execute, l'objet d'exception est obtenu via AcquireExceptionObject et stocké dans le membre FFatalException de la classe de thread.
Enfin, il y a quelques touches finales avant la fin du fil. La variable locale FreeThread enregistre le paramètre de l'attribut FreeOnTerrated de la classe de thread, puis définit la valeur de retour du thread sur la valeur de l'attribut de valeur de retour de la classe de thread. Exécutez ensuite la méthode DoTerminate de la classe thread.
Le code de la méthode DoTerminate est le suivant :
procédure TThread.DoTerminate ;
commencer
si attribué (FOnTerminate) alors Synchronize (CallOnTerminate);
fin;
C'est très simple, il suffit d'appeler la méthode CallOnTerminate via Synchronize, et le code de la méthode CallOnTerminate est le suivant, qui consiste simplement à appeler l'événement OnTerminate :
procédure TThread.CallOnTerminate ;
commencer
si assigné (FOnTerminate) alors FOnTerminate (Self);
fin;
Étant donné que l'événement OnTerminate est exécuté dans Synchronize, il ne s'agit essentiellement pas du code du thread, mais du code du thread principal (voir l'analyse de Synchronize plus tard pour plus de détails).
Après avoir exécuté OnTerminate, définissez l'indicateur FFinished de la classe de thread sur True.
Ensuite, le processus SignalSyncEvent est exécuté et son code est le suivant :
procédure SignalSyncEvent ;
commencer
SetEvent(SyncEvent);
fin;
C'est également très simple, il suffit de définir un événement global : SyncEvent. Cet article décrira l'utilisation d'Event en détail plus tard, et le but de SyncEvent sera expliqué dans le processus WaitFor.
Ensuite, il est décidé de libérer ou non la classe de thread en fonction du paramètre FreeOnTerminate enregistré dans FreeThread. Lorsque la classe de thread est publiée, certaines opérations sont effectuées. Voir l'implémentation du destructeur suivant pour plus de détails.
Enfin, EndThread est appelé pour terminer le thread et la valeur de retour du thread est renvoyée.
À ce stade, le fil est complètement terminé.
(à suivre)