|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 |
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 DevStudioVoici 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’éditeurIl 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 OutputElle 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 WizardA 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 MFCTous 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 CWinAppPour 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 CWinAppPendant 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
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 CCmdTargetLe 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 / VueLa 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 SDICette 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 MDIDans 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ôlesLes 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éationLe 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énementsDans 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ôlesIl 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 ThreadUne 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 ThreadsIl 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 threadsIl 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 ThreadLes 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 threadsLa 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 sectionsLes 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 mutexesLes 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émaphoresLes 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énementsLes 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++
Retour à la table des matières |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Toutes les images et tous sons de Multithread en Visual C++ sont déposé par |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Dernière Mise à Jour : 2000/06/26 {=compt_puce_n} |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||