Index Multithread en Visual C++ / Ambre et Michael - 1998/02
   Manga
   Jeux de Rôle
   Informatique
   Liens



Etat
   Home
       Informatique
           Multithread en Visual C++



Aperçu


Multithread en Visual C++




Recherche

    Rumiel's Web Page
    Web Mondial



Powered By WhatUSeek.com


Table des matières

   Partie 1 : Introduction à Visual C++ 5.0
       Présentation succincte de l’interface DevStudio
           Le 'Project Workspace'
           L’éditeur
           La fenêtre Output
           Le Class Wizard
       La Bibliothèque de classes MFC
       MFC et les applications et architecture d'une application
           Architecture de l'application
           La classe d'application CWinApp
           Héritage de CWinApp
               CWinThread
               CCmdTarget
               CObject
       Architecture Document / Vue
           Les SDI
           Les MDI
       Gestion des contrôles
           Création
           Création d’événements
           Agir sur les contrôles
   PARTIE 2 : Gestion du multithreading
       Gestion des processus en visual C++
           Définition d'un processus
           Définition d'un Thread
       La programmation de Threads
           Création de threads
           Fin de Thread
       Synchronisation des threads
           Les critical sections
           Les mutexes
           Les sémaphores
           Les événements
   Liens

Partie 1 : Introduction à Visual C++ 5.0

Visual C++ est un compilateur C/C++ complet permettant de développer des applications non seulement sous des environnements 32 bits tel que Windows 95 ou Windows NT mais également sous des systèmes 16 bits tel que Windows 3.x et MS-DOS.

En plus des applications, Visual C++ permet de créer ses propres DLL (Dynamic Link Libraries), librairie de fonctions accessibles par les applications, des contrôles activeX (OCX) personnalisés ainsi que la création de COM (Component Object Model).

L'interface de développement, le "DevStudio" comprend divers outils que nous allons vous décrire.

Retour a la table des matières

    Présentation succincte de l’interface DevStudio

Voici en gros à ce à quoi vous êtes confrontés lors du lancement de Visual C++ 5.0 :

visualisation du MicroSoft Developer Studio

Le DevStudio se décompose principalement en trois fenêtres :

Le "Project Workspace" (à gauche), L'éditeur (à droite), La fenêtre Output (en bas).

        Le 'Project Workspace'

La fenêtre située au milieu et à gauche du développer studio, est vraiment utile pour l'utilisation de l'environnement de développement. Sa fonction principale est d’aider le développeur à accéder rapidement aux différents éléments composant son projet. Ceux-ci seront accessibles via différents onglets : L'onglet ClassView permet l’accès aux différentes classes définies dans le projet. Une découpe en arborescence permet, dans ces mêmes classes, d’accéder et d’ajouter des fonctions ou variables membres. L'onglet RessourceView donne accès aux ressources du projet. Par ressource, on entend les menus, les boîtes de dialogues, les touches de raccourci, les fichiers graphiques, la " string table " (table contenant différentes constantes facilitant l’identification des ressources, entre autres ...), etc... L'onglet FileView donne la liste des fichiers composant le projet. L’onglet InfoView permet l’accès à l’aide en ligne.

Retour a la table des matières

        L’éditeur

Il s’agit d’un éditeur tout ce qui a de plus banal. Notons cependant qu’il intègre un éditeur graphique permettant de modifier les ressources comme les fichiers Bitmap ou les icônes. Lors de la codification, l’éditeur modifie la couleur du texte selon que l’on utilise des types de base, que l’on insère des commentaires etc. Bref, comme tout éditeur de sources qui se respecte.

Retour a la table des matières

        La fenêtre Output

Elle sert de zone de communication lors des compilations. C’est ici que l’on retrouvera les messages d’erreurs et/ou le résultat de la compilation.

Retour a la table des matières

        Le Class Wizard

A tout moment, un clic sur le bouton droit de la souris permettra l’accès à différentes opérations ainsi qu’au " Class Wizard ". Le Class Wizard, (l’assistant de classe) est un outil très important car il permet, non seulement, de modifier, créer ou dériver les classes du projet mais également l’ajout de messages (comme la gestion des événements de contrôles).

Retour a la table des matières

    La Bibliothèque de classes MFC

Tous ceux qui ont déjà fait de la programmation sous Windows, ont sans aucun doute eu à faire usage des fonctions API. Les fonctions de l'API (Application Programming Interface) constituent l'interface entre les applications et le système d'exploitation. Ce sont les fonctions de l'API qui permettront à vos applications de tirer parti de toute la richesse de l'environnement Windows et notamment de son interface utilisateur. Il faut savoir, cependant, que ces fonctions sont souvent compliquées à utiliser et qu'elles se situent à un autre niveau d'abstraction. C’est pourquoi, depuis quelques années, Microsoft a défini un ensemble de classes hiérarchisées englobant les différents objets composant ou composés par le système : les Microsoft Foundation Classes ( MFC pour les intimes ...).

Image reprennant l'arborescence des Microsoft Foundation Classes verson 6.0

Depuis la version 4 de Visual C++, les MFC permettent de développer des applications 32 bits pour des plates-formes telles que Windows 95 ou Windows NT.

Bon nombres d’API ont été encapsulées dans les classes, rendant leur usage beaucoup plus intuitif et plus simple. Les MFC apportent pratiquement tout ce qu'il faut pour écrire des applications Windows. Cependant, même si vous bâtissez vos applications sur les classes MFC, rien ne vous empêche à tout instant d'accéder directement aux API de Windows. C'est parfois indispensable pour faire telle ou telle tâche spéciale que seul des parties du système utilise généralement. (Accéder à certains paramètres de fenêtre,Appeler des fonctions système de MS-DOS, Utiliser des pointeurs far dans vos sources C++, ... )

Retour a la table des matières

    MFC et les applications et architecture d'une application

 

        Architecture de l'application

L'assistant AppWizard fournit une charpente à l'application. Ce squelette de programme peut comprendre bien des choses : une interface MDI, ou SDI, des boîtes de dialogue, des barres d'outils, etc. Ce squelette permet au développeur de se concentrer essentiellement sur le code même de l'application. La charpente ainsi créée reste un ensemble de classes et de structures accessibles et réutilisables pour les besoins propres de l'application. Mais voyons ce que cela donne dans la pratique. Lorsqu'on génère une application via l'AppWizard, on reçoit une série de fichiers. A quoi servent-ils ? Et bien, ils répondent à une découpe du code de l'application visant à garder un maximum d'indépendance entre les divers éléments de l'application. Voyons le cas d'un projet X généré en tant que SDI (single document interface):

Nom du fichier : X.cpp, Classe de base MFC : CWinApp, Classe dérivée : CXApp, Fonction : C’est le noyau de l’application, le centre des messages et du traitement.

Nom du fichier : Xdoc.cpp, Classe de base MFC : CDocument, Classe dérivée : CXDoc, Fonction : Implémentation du document

Nom du fichier : MainFrm.cpp, Classe de base MFC : CFrameView, Classe dérivée : CMainFrame, Fonction : Fenêtre principale du SDI

Nom du fichier : Xview.cpp, Classe de base MFC : CView, Classe dérivée : CXView, Fonction : Implémentation de la vue

Retour a la table des matières

        La classe d'application CWinApp

Pour créer une application Windows basée sur les MFC, vous passerez le plus souvent par les divers outils de Visual C++, bien que cela ne soit pas obligatoire . Au lieu d'appeler AppWizard pour bâtir le squelette d'une application, vous pourriez écrire vous-même le code source que AppWizard aurait généré pour vous.

AppWizard lors de la création d'un projet génère la classe CXApp {pour un projet portant le nom X} qui représente la classe principale de l'application. De cette classe il a déjà déclaré l’instance nommée theApp (dans CXApp.h):

...

CXApp theApp ;

...

Vous constaterez également que quelques lignes plus haut, CXApp dérive de la classe CWinApp :

...

class CXApp : public CWinApp

...

CWinApp est la classe qui définit un objet application. Pour toute application MFC il faut dériver une (et une seule) classe CWinApp. Cette classe de l'application renferme toutes les méthodes permettant d'initialiser et de démarrer l'application.

Retour a la table des matières

        Héritage de CWinApp

Pendant que l'occasion se présente, un brin d'éclaircissement sur la classe CWinApp s'impose. Il ne s'agit pas d'une classe de base MFC mais d'une dérivation de plusieurs autres classes importantes. En effet, voici l’héritage de cette classe:

Retour a la table des matières

            CWinThread

La classe CWinApp dérive directement de la classe CWinThread. Preuve que l’application est bien un thread (on l’appelle d’ailleurs le Thread primaire). Notons que ce qui différencie justement les systèmes 16 bits (Win 3.11) des 32 bits (Win 95 et NT) est la possibilité de ces derniers à gérer des applications possédant plusieurs threads (les threads secondaires). Cette classe gère donc tout ce que l’application est en tant que tâche (unité de traitement) elle conditionne le vie et la mort de l’application.

Retour a la table des matières

            CCmdTarget

Le parent suivant est CCmdTarget. Cette classe intègre la gestion des messages. La gestion des messages se fait grâce à des message maps c.à.d. des tables propres à chaque éléments de l’application (et non pas à l’application !). Dans ces tables, on retrouve des closes ON_... contenant un identifiant correspondant à un événement (un clic sur un bouton ...) et une adresse de fonction gérant cet événement:

Retour a la table des matières

BEGIN_MESSAGE_MAP(CHelloView, CFormView)

//{{AFX_MSG_MAP(CHelloView)

ON_BN_CLICKED(IDC_BUTTON1, OnButton1) // si clic sur bouton …

ON_BN_CLICKED(IDC_RADIO1, OnRadio1)

ON_BN_CLICKED(IDC_RADIO2, OnRadio2)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

Lorsqu’un utilisateur produit un événement, Windows (le système, si, si ...) le réceptionne identifie son propriétaire (le thread) et notifie dans sa message queue l’événement. Il ne reste plus à la tâche visée que de se router sur la fonction adéquate. Cependant, si la message queue est vide, la tâche rentrera dans la méthode OnIdle(). Ce système évite au programmeur de fastidieuses boucles d’attente composées de switch interminables.

            CObject

Cette classe est la classe de base de référence. Elle est " l’ancêtre " d’environ 90% des objets MFC. Elle ne contient pas grand chose si ce n’est des informations pour la gestion de la mémoire et pour le déboguage.

Retour a la table des matières

    Architecture Document / Vue

La grande majorité des applications ne vise qu’un but, donner accès a des données formatées dans un interface qui les transformera en information pour l’utilisateur. De plus, de nos jours, une règle fondamentale est d’application : INDEPENDANCE des données vis-à-vis de l’interface pour une plus grande évolutivité. Conscient de cet état de fait, les MFC furent dotées d’une architecture dite Document / Vue. Dans le modèle document / vue, la facette (la classe) concernant les données s'appelle le document. En théorie, elle devrait totalement encapsuler les données que l'application manipule. Vous l'aurez deviné, la classe gérant l'interface s'appelle la Vue. Elle reprend le code servant à transformer les données en information à l'utilisateur. Bien entendu, cette architecture n'est pas adaptée à tout les types d'application. C'est pourquoi l'AppWizard permet la création d'autre type d'application tel que les application basées sur les dialogue, ou les applications consoles (mode texte à la MS-DOS). En pratique, à l’Initialisation, l’application va créer une instance appelée Template qui lui définira quelles classes de document et de vue seront utilisées:

CHelloApp: :InitInstance() {

...

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CHelloDoc),

RUNTIME_CLASS(CMainFrame), // main SDI frame window

RUNTIME_CLASS(CHelloView));

AddDocTemplate(pDocTemplate);

...

Comme on peut le constater, un template ne possède qu’une seule instance de document et de vue. L’adresse du template sera sauvegardée par la fonction AddDocTemplate.

Il existe deux modèles de type document vue, qui différent légèrement quant à leur dérivation : les SDI (Single Document Interface) et les MDI (Multiple Document Interface).

Retour a la table des matières

        Les SDI

Cette architecture ne permet d’utiliser qu’un et un seul template à la fois. La fenêtre principale sera entièrement occupée par la Vue en cours de traitement et l’ouverture d’un autre document referme le précédent. Exemple d’application SDI : le bloc-notes de Windows. Voici le schéma de la hiérarchie existant au sein de l’application :

Les flèches pleines indiquent la création et les flèches pointillées les pointeurs existants. On notera le lien réciproque qui existe entre le document et la vue .

Retour a la table des matières

        Les MDI

Dans une application MDI plusieurs documents peuvent être ouverts en même temps, chacun dans sa propre fenêtre. Cette architecture est très pratique si l’on veut par exemple, avoir deux vues différentes sur des données affichées simultanément. La hiérarchie diffère légèrement de celle des SDI :

On notera que le template ne pointe plus sur la vue mais bien sur le document. Cependant, ces différences ne prêtent pas à des modifications de conceptions importantes (les classes Single... et Multi… étant fort voisines) et sont quasi transparentes au programmeur. Exemple d’application MDI : Word, Excel.

Retour a la table des matières

    Gestion des contrôles

Les contrôles permettent la communication entre l’utilisateur et l’application. Les contrôles sont naturellement des objets appartenant à la vue. Un ensemble de contrôles appartenant à une même vue s’appelle un dialogue. Ils sont à la base d’événements comme on a pu le constater précédemment.

Retour a la table des matières

        Création

Le dialogue est une ressource. Pour dessiner l’écran de dialogue, Visual C++ met à la disposition du développeur un éditeur de dialogue qui est accessible lorsque l’on sélectionne la ressource via le Project Workspace. Pour chaque contrôle inséré, le Class Wizard lui associera un identifiant du style IDC_.... Cet identifiant permettra l’accès non ambigu au contrôle.

Retour a la table des matières

        Création d’événements

Dans l’onglet Message maps du Class wizard, il suffit de sélectionner l’identifiant du contrôle. Une liste d’événements associables apparaît. Pour sélectionner un événement, on double-clique sur celui-ci. Il ne reste plus dès lors qu’à éditer le code spécifique à cet événement.

Retour a la table des matières

        Agir sur les contrôles

Il existe deux méthodes permettant de modifier dynamiquement les propriétés d’un contrôle. La première, la plus générale, vise à créer un pointeur contenant l’adresse du contrôle. A partir de là, les méthodes associées à ce contrôle seront accessibles :

...

CEdit *pEd ;

pE = (Cedit *) GetDlgItem(IDC_EDIT1) ;

pE->SetWindowText(" Voici un nouveau message. ") ;

...

La fonction GetDlgItem permet de récupérer l’adresse d’un objet dialogue. Elle reçoit en paramètre l’identifiant du contrôle. Notons que le casting de l’adresse renvoyée est obligatoire. SetWindowText est quant à elle une méthode générale aux objets visibles. Elle permet de modifier la chaîne qu’affiche l’objet (la propriété text pour un EditBox, le caption d’un bouton etc…). Il s’agit bien d’un objet et non d’un contrôle car cette fonction permet également de modifier le titre d’une fenêtre par exemple.

La deuxième méthode est toute nouvelle depuis la version 5 de Visual C++. Elle a été baptisée DDX/DDV (pour Dialog Data Exchange and Validation). Pour l’utiliser, il suffit de créer une variable associée à un contrôle (via l’onglet Member Variables du Class Wizard). Pour modifier cette variable, on signalera au programme la mise à jour de celle-ci via la fonction UpdateData(TRUE). On assigne la nouvelle valeur de la variable, puis on notifie que la mise à jour est terminée en appelant la fonction UpdateData(FALSE). Le contrôle sera automatiquement mis à jour :

...

UpdateData(TRUE) ;

m_edit1 = " Voici un nouveau message " ;

Updatedata(FALSE) ;

...

Cette méthode de travail permet également la validation automatique de la donnée insérée. Le domaine valide est définissable lors de la création de la variable membre.

Retour a la table des matières

PARTIE 2 : Gestion du multithreading

 

    Gestion des processus en visual C++

 

        Définition d'un processus

Un processus est un espace comprenant une ou plusieurs threads et les ressources nécessaires au déroulement de celles-ci. Un processus possède en outre une priorité d'exécution reprise dans la liste ci-dessous.

Priorité du processus : REALTIME_PRIORITY_CLASS - Valeur de base accordée : 24

Priorité du processus : HIGH_PRIORITY_CLASS - Valeur de base accordée : 13

Priorité du processus : NORMAL_PRIORITY_CLASS - Valeur de base accordée : 9 en premier plan, 7 en arrière plan

Priorité du processus : IDLE_PRIORITY_CLASS - Valeur de base accordée : 4

La priorité du processus est modifiable via SetPriorityClass() :

BOOL SetPriorityClass( HANDLE hProcess, DWORD dwPriorityClass );

hProcess : handle du processus visé.

dwPriorityClass : nouvelle priorité visée (cfr. tableau ci-dessus).

Pour récupérer la priorité d'un processus, on utilise GetPriorityClass() :

DWORD GetPriorityClass( HANDLE hProcess);

Pour récupérer le handle du processus, on utilise GetCurrentProcess() :

HANDLE GetCurrentProcess() ;

Retour a la table des matières

        Définition d'un Thread

Une thread, ou " thread of exécution ", représente l'information la plus essentielle que le processeur nécessite pour l'exécution d'une tâche. Une thread est donc une entité sélectionnable par le CPU totalement dépendante de son contexte. Celui-ci comprend les différentes variables utilisées et les adresses des fonctions pour les applications, ou la table d'interruption et l'ensemble des registres du processeur pour les fonctions systèmes ajoutées au kernel. Le processeur exige donc de connaître le contexte des threads pour pouvoir les exécuter et passer d'une thread à une autre. Le processeur réalise une thread jusqu'à ce que celle-ci soit achevée ou jusqu'à ce que la part de temps impartie à cette thread soit dépassé, il sauvera alors le contexte de la thread pour pouvoir la continuer plus tard.

Une thread possède également une priorité d'exécution. Le sélectionneur de tâche choisit un thread en vue de l'exécution suivant sa priorité. Cette priorité dépend essentiellement de la priorité du processus à qui appartient cette thread. La liste ci-dessous reprend les valeurs accordées au processus et aux threads suivant leur priorité.

Priorité de la thread : THREAD_PRIORITY_TIME_CRITICAL - Ajustement de la valeur accordée : 31 pour les thread appartenant à un processus " temps réel ", 15 pour les autres threads

Priorité de la thread : THREAD_PRIORITY_HIGHEST - Ajustement de la valeur accordée : + 2 à la valeur accordée au processus

Priorité de la thread : THREAD_PRIORITY_ABOVE_NORMAL - Ajustement de la valeur accordée : + 1 à la valeur accordée au processus

Priorité de la thread : THREAD_PRIORITY_NORMAL - Ajustement de la valeur accordée : Même valeur que celle accordée au processus

Priorité de la thread : THREAD_PRIORITY_BELOW_NORMAL - Ajustement de la valeur accordée : -1 que la valeur accordée au processus

Priorité de la thread : THREAD_PRIORITY_LOWEST - Ajustement de la valeur accordée : -2 que la valeur accordée au processus

Priorité de la thread : THREAD_PRIORITY_IDLE - Ajustement de la valeur accordée : 15 pour les threads appartenant à un processus " temps réel ", 1 pour les autres threads

Priorité de la thread : THREAD_PRIORITY_ERROR_RETURN - Ajustement de la valeur accordée : 0 (Indique une erreur de priorité quand on crée une nouvelle thread)

La priorité d'une thread est complétée par celle du processus. Le choix de la priorité du processus et d'une thread doit donc être en relation avec ce que celui ou celle-ci devra effectuer. En effet, un thread ayant une priorité TIME_CRITICAL risque fort de s’approprier tout le temps CPU et de s’approprier la main suffisamment longtemps pour lasser l’utilisateur.

Pour assigner une priorité à une thread, on utilise la méthode SetThreadPriority() :

BOOL CWinThread : :SetThreadPriority( int nPriority);

nPriority : niveau de priorité désiré.(voir table ci-dessus)

Si l’on veut augmenter la priorité d’un thread temporairement et que l’on ne connaît pas sa valeur à l’avance, on peut récupérer cette valeur grâce à la méthode GetThreadPriority() :

DWORD CWinThread : :GetThreadPriority() ;

Et si on ne connaît pas l’adresse du thread en cours, il existe la fonction AfxGetThread() :

CWinThread* AfxGetThread() ;

Retour a la table des matières

    La programmation de Threads

Il ne faut pas perdre de vue que la gestion des threads prend du temps machine. Celles-ci ne doivent être invoquées que pour des problèmes nécessitant que plusieurs tâches soient exécutées en même temps. La plupart du temps, les threads sont utilisées pour permettre de s'occuper d'un traitement réel pendant qu'une autre regarde si les E/S plus lentes s'effectuent correctement. Il est important de bien définir ce que les threads doivent gérer et qu'elles ne se gênent pas.

Retour a la table des matières

        Création de threads

Il existe deux manières de créer des threads dans des applications utilisant les MFC. Chacune d’elles utilisent la fonction AfxBeginThread() qui accepte deux " overloads ".

La première méthode, sans doute la plus simple, permet d’attacher à un thread une fonction de traitement. Cette fonction remplacera la méthode CWinThread : :Run() et devra se comporter de la même manière :

CWinThread* AfxBeginThread( RUN_TIME_CLASS pThreadProc,

LPVOID pParm,

Int nPriority = THREAD_PRIORITY_NORMAL,

UINT nStackSize = 0 ,

DWORD dwCreateFlags = 0,

LPSECURITY_ATTRIBUTES

lpSecurityAttrs = NULL) ;

pThreadProc : pointeur vers la fonction contrôlant la thread.

pParm : paramètre libre (ni Windows, ni l’application n’y font appel). On peut donc l’utiliser pour transmettre l’adresse d’un contrôle ou d’une fenêtre qui sera la cible du thread. ATTENTION : si on transmet une adresse d’objet, la fonction pThreadProc DOIT être définie comme amie au sein de l’objet.

nPriority : définit la priorité initiale de la thread.

nStackSize : taille de la pile réservée à la thread. Normalement, le système fera des allocations suivant les besoins. Cependant, pour éviter des allocations successives, on peut définir une taille de base.

dwCreateFlag : définit le comportement du thread après la création :

0 : crée la thread et lui permet l'exécution

CREATE_SUSPENDED : la thread est créée, mais ne sera exécuté que lorsque la méthode CWinThread::ResumeThread() sera appelée.

lpSecurityAttrs : pointeur vers une structure d'attribut de sécurité. Pour initialiser votre propre structure utilisez la fonction : :InitializeSecurityDescriptor().

Exemple :

CWinThread* pThread ;

pThread = AfxBeginThread ( Tourne, this, THREAD_PRIORITY_LOWEST );

Cette méthode permet de créer un thread de manière peu " coûteuse ". Cependant, elle présente certains désavantages comme l’absence de contrôle sur la MESSAGE_MAP ou sur l’Initialisation du thread.

La deuxième méthode consiste à dériver sa propre classe de CWinThread. On aura dés lors tout le loisir " d’overrider " les méthodes voulues et d’avoir également accès à la gestion des messages :

CWinThread* AfxBeginThread( CwinThread *pMonThread,

int nPriority = THREAD_PRIORITY_NORMAL,

UINT nStackSize = 0 ,

DWORD dwCreateFlags = 0,

LPSECURITY_ATTRIBUTES

lpSecurityAttrs = NULL) ;

pMonThread : pointeur sur la classe dérivée.

Les autres paramètres : idem que plus haut.

Messages entre Threads

Chaque thread possède une file d'attente de message. Il est donc possible de communiquer avec celles-ci. Sous Win16 il était uniquement possible d'envoyer un message à une fenêtre. Sous Win32, par contre, il est possible d'envoyer un message à une fenêtre ou directement à une thread en utilisant soit la méthode CWinThread : :PostThreadMessage() :

CWinThread : :PostThreadMessage( UINT Idmsg, ,) ;

Idmsg : un numéro identifiant le message.

Pour réceptionner les messages envoyer à une thread en utilisant la macro ON_THREAD_MESAGE() ou ON_REGISTRED_THREAD_MESSAGE() :

BEGIN_MESSAGE_MAP(MaThread, CWinThread)

ON_THREAD_MESSAGE(ID_MSG_TERMINATE, OnTerminate)

ON_REGISTRED_THREAD_MESSAGE(nSequenceStartMsg,

OnStartSequence)

END_MESSAGE_MAP(MaThread)

ID_MSG_TERMINATE : identifiant du message

OnTerminate : fonction qui va traiter le message

nSequenceStartMsg : identifie la variable qui contient la valeur retournée par la fonction

OnStartSequence : fonction qui va traiter le message

Retour a la table des matières

        Fin de Thread

Les threads peuvent se terminer de deux manière différentes : naturellement, une fois que la tâche pour laquelle elle a été définie est terminée ou, pour être plus explicite lorsque la méthode CWinThread : :Run() retourne une valeur. Ou bien encore prématurément, quand elle reçoit un message lui disant de se terminer.

Pour les threads dérivées des MFC, on utilise la fonction PostQuitMessage() qui va détruire la thread :

VOID PostQuitMessage( int nExitCode);

nExitCode : code de retour du thread.

Exemple : Ma_Thread : :Run ()

{

...

if (erreur)

{

PostQuitMessage(1) ;

}

else {...}

}

Il existe également la fonction AfxEndThread() :

void AfxEndThread( UINT nExitCode );

Exemple :

Ma_Thread()

{

...

if (erreur)

{

AfxEndThread(return_code_erreur) ;

}

else {...}

return(return_code_OK) ;

}

Il est possible d'utiliser les fonctions de l'API telles que : :TerminateThread(), qui peut être appelé de n'importe où (du processus père, d'un autre processus, d'une autre thread), mais ceci est plus dangereux que AfxEndThread. En effet, TerminateThread() termine le thread, point. Aucune désallocation n’est faite, si bien que si ce schéma est répété, la mémoire va disparaître petit à petit. Windows NT protégera celle-ci et le reste du système, mais Windows 95 n'y prêtera pas attention.

Retour a la table des matières

    Synchronisation des threads

La synchronisation est un concept important dans la programmation de threads. Et plus particulièrement lorsque plusieurs threads accèdent et modifient des données communes. Que se passerait-il par exemple si un thread voulait lire une liste linéaire pendant qu’un autre est en train d’y enchaîner un nouvel élément. C’est le plantage assuré. C’est pourquoi, des classes ont été créées afin de protéger des ressources sensibles de tout " viol ".

Retour a la table des matières

        Les critical sections

Les critical sections sont les objets de synchronisation les plus légers. Elles sont utiles lorsque une ressource au sein d’un processus ne doit être accédée que par un seul thread à la fois. Elles seront une solution préférable aux mutexes si le facteur performance (vitesse d’exécution) est important. Leur implémentation est très simple. Il suffit d’inclure une instance de la classe CCriticalSection à la ressource sensible :

class CDocument : public CmonDoc {

...

CCriticalSection m_serrure ;

...

Lorsqu’un thread voudra accéder à cette ressource, il demandera le contrôle de celle-ci via la méthode Lock() :

CmaThread : :Run() {

...

pDoc->m_serrure.Lock() ;

// modification

pDoc->m_serrure.UnLock() ;

...

}

Comme le montre l’exemple, après l’accès à la ressource, le thread doit libérer la ressource en utilisant la méthode UnLock().

Si un thread demande l’accès à la ressource alors qu’elle est déjà acquise par un autre thread, il sera bloqué jusqu’à ce que celle-ci se libère. Ce qui peut se concevoir comme un inconvénient. Les critical sections n’implémentent pas, en effet, de méthode du style IsLocked() ou autre pour vérifier l’état d’une ressource.

Retour a la table des matières

        Les mutexes

Les mutexes (mutually exclusives) sont fort similaires aux critical sections si ce n’est qu’elle offrent plus de fonctionnalités (au dépend des performances). Leur création est similaire aux critical sections :

class Cressource : public Cmaressource {

...

CMutex m_mutex ;

...

Qu’est-ce que les mutexes offrent donc de plus ? Premièrement, un thread n’est plus obligé d’attendre qu’une ressource se libère pour accomplir sa tâche. En effet, la classe CMutex implémente la méthode IsLocked() qui retourne TRUE si la ressource est prise (ce qui est quand même pratique). Deuxièmement, les mutexes gèrent la protection de ressources multiples : Si on a plusieurs ressources à protéger, et que un thread nécéssite l’accès a ces ressources, il peut effectuer une requête multiple. Pour que le thread puisse accéder à la ressource, il faut déclarer la synchronisation en son sein. Pour ce faire, il existe deux méthodes, suivant le nombre de ressources à accéder. Si le thread n’accède qu’à une seule ressource, il faut déclarer la synchronisation comme de type CSingleLock :

CSingleLock(CSyncObject *pObject,BOOL bInitialLock = FALSE) ;

pObject : l’adresse de l’objet contrôlant la synchronisation (ici, le mutex).

bInitialLock : booléen indiquant si la ressource doit être directement accédée.

Exemple :

...

CSingleLock sLock(&(m_pOwner->m_mutex));

...

A partir de là, le bloquage d’une ressource se fera classiquement par l’appel de la méthode Lock() et Unlock() :

sLock.Lock() ;

...

sLock.Unlock() ;

Par contre, si le thread désire accéder à plusieurs ressources, le même mécanisme sera utilisé mais avec la classe CmultiLock :

CMultiLock( CSyncObject* ppObjects[ ], DWORD dwCount, BOOL bInitialLock = FALSE );

PObjects : tableau d’adresse des objets devant être accédés.

dwCount : le nombre d’objets devant être accédés.

bInitialLock : idem que CSingleLock.

Retour a la table des matières

        Les sémaphores

Les sémaphores sont une forme très particulière de synchronisation. Elles sont principalement utiles lors du développement d’applications Client/Serveur. Elles permettent, d’une part, la synchronisation de ressources entre plusieurs processus, et d’autre part, la limitation d’accès aux ressources à un nombre limité de threads. Donc, contrairement aux critical sections et aux mutexes, qui ne permettait l’accès qu’à un thread à la fois, les sémaphores permettent l’accès à plusieurs threads jusqu’à atteindre un seuil limite. Les sémaphores ont été cités à titre de " culture générale " car leur usage dépasse largement le cadre de ce document.

Retour a la table des matières

        Les événements

Les objets de la classe CEvent permettent aux threads de notifier à d’autres threads qu’un événement s’est produit. Cette méthode est intéressante lorsqu’un thread doit savoir quand il peut effectuer sa tâche.

Les événements se déclarent de manière similaire aux mutexes (en single ou multi lock). Cependant, leurs méthodes diffèrent :

SetEvent() : signale que l’objet est accessible. Un thread en attente acquerra la ressource. La ressource devra être libérée par un Unlock().

ResetEvent() : Annule un SetEvent().

Retour a la table des matières


   Liens pour Multithread en Visual C++
    Microsoft : page sur le Visual C++ en français

Retour à la table des matières



|  Rumiel's Home Page |  Informatique |


Toutes les images et tous sons de Multithread en Visual C++ sont déposé par
© Bruno Bellamy. All Rights Reserved.
© Microsoft MSDN

   


Dernière Mise à Jour : 2000/06/26

{=compt_puce_n}