Multithreading : utilisation des classes de synchronisation MFC
La synchronisation de l’accès aux ressources entre les threads est un problème courant lors de l’écriture d’applications multithread. Un ou plusieurs threads accèdent simultanément aux mêmes données peuvent entraîner des résultats indésirables et imprévisibles. Par exemple, un thread peut mettre à jour le contenu d’une structure tandis qu’un autre thread lit le contenu de la même structure. Il est inconnu les données que le thread de lecture recevra : les anciennes données, les données nouvellement écrites, ou éventuellement un mélange des deux. MFC fournit un certain nombre de classes d’accès de synchronisation et de synchronisation pour faciliter la résolution de ce problème. Cette rubrique explique les classes disponibles et comment les utiliser pour créer des classes thread-safe dans une application multithread classique.
Une application multithread classique a une classe qui représente une ressource à partager entre des threads. Une classe entièrement thread-safe correctement conçue ne vous oblige pas à appeler des fonctions de synchronisation. Tout est géré en interne pour la classe, ce qui vous permet de vous concentrer sur la meilleure utilisation de la classe, et non sur la façon dont elle peut être endommagée. Une technique efficace pour créer une classe thread-safe complète consiste à fusionner la classe de synchronisation dans la classe de ressources. La fusion des classes de synchronisation dans la classe partagée est un processus simple.
Par exemple, prenez une application qui gère une liste liée de comptes. Cette application permet d’examiner jusqu’à trois comptes dans des fenêtres distinctes, mais une seule peut être mise à jour à tout moment. Lorsqu’un compte est mis à jour, les données mises à jour sont envoyées sur le réseau à une archive de données.
Cet exemple d’application utilise les trois types de classes de synchronisation. Étant donné qu’il permet d’examiner jusqu’à trois comptes à la fois, il utilise CSemaphore pour limiter l’accès à trois objets d’affichage. Lorsqu’une tentative d’affichage d’un quatrième compte se produit, l’application attend que l’une des trois premières fenêtres se ferme ou qu’elle échoue. Lorsqu’un compte est mis à jour, l’application utilise CCriticalSection pour s’assurer qu’un seul compte est mis à jour à la fois. Une fois la mise à jour réussie, elle signale CEvent, qui libère un thread en attente de l’événement à signaler. Ce thread envoie les nouvelles données à l’archive de données.
Conception d’une classe thread-safe
Pour rendre une classe entièrement thread-safe, commencez par ajouter la classe de synchronisation appropriée aux classes partagées en tant que membre de données. Dans l’exemple de gestion de compte précédent, un CSemaphore
membre de données serait ajouté à la classe d’affichage, un CCriticalSection
membre de données serait ajouté à la classe de liste liée et un CEvent
membre de données serait ajouté à la classe de stockage de données.
Ensuite, ajoutez des appels de synchronisation à toutes les fonctions membres qui modifient les données de la classe ou accèdent à une ressource contrôlée. Dans chaque fonction, vous devez créer un objet CSingleLock ou CMultiLock et appeler la fonction de cet Lock
objet. Lorsque l’objet de verrouillage sort de l’étendue et est détruit, le destructeur de l’objet vous appelle Unlock
, en libérant la ressource. Bien sûr, vous pouvez appeler Unlock
directement si vous le souhaitez.
La conception de votre classe thread-safe de cette façon lui permet d’être utilisée dans une application multithread aussi facilement qu’une classe non thread-safe, mais avec un niveau de sécurité supérieur. L’encapsulation de l’objet de synchronisation et de l’objet d’accès à la synchronisation dans la classe de la ressource offre tous les avantages de la programmation entièrement thread-safe sans l’inconvénient de la gestion du code de synchronisation.
L’exemple de code suivant illustre cette méthode à l’aide d’un membre de données ( m_CritSection
de type CCriticalSection
), déclaré dans la classe de ressource partagée et dans un CSingleLock
objet. La synchronisation de la ressource partagée (dérivée de CWinThread
) est tentée en créant un CSingleLock
objet à l’aide de l’adresse de l’objet m_CritSection
. Une tentative est effectuée pour verrouiller la ressource et, lorsqu’elle est obtenue, le travail est effectué sur l’objet partagé. Une fois le travail terminé, la ressource est déverrouillée avec un appel à Unlock
.
CSingleLock singleLock(&m_CritSection);
singleLock.Lock();
// resource locked
//.usage of shared resource...
singleLock.Unlock();
Remarque
CCriticalSection
, contrairement à d’autres classes de synchronisation MFC, n’a pas la possibilité d’une demande de verrouillage chronométrée. La période d’attente pour qu’un thread devienne libre est infinie.
Les inconvénients de cette approche sont que la classe sera légèrement plus lente que la même classe sans les objets de synchronisation ajoutés. En outre, s’il est possible que plusieurs threads puissent supprimer l’objet, l’approche fusionnée peut ne pas toujours fonctionner. Dans ce cas, il est préférable de conserver des objets de synchronisation distincts.
Pour plus d’informations sur la détermination de la classe de synchronisation à utiliser dans différentes situations, consultez Multithreading : Quand utiliser les classes de synchronisation. Pour plus d’informations sur la synchronisation, consultez Synchronisation dans le Kit de développement logiciel (SDK) Windows. Pour plus d’informations sur la prise en charge de multithreading dans MFC, consultez Multithreading avec C++ et MFC.