Aller au menu - Aller au contenu

Icône Les threads et les mutex

Avatar
Mise à jour : 05/05/2009
Difficulté : Intermédiaire Intermédiaire
3 369 visites depuis 7 jours, dont 996 sur ce chapitre classé 48/786
Dans ce chapitre, nous allons parler des threads, des processus et des mutex. Il est relativement important car il constitue la partie théorique du cours sur les threads et permet d'installer une bibliothèque nommée Pthread ;) .
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Quelques définitions

Les processus



Un processus est une tâche qui est en train de s'exécuter.
Par exemple, quand vous lancez un de vos programmes que vous avez développé, votre OS crée un nouveau processus et celui-ci exécutera une suite d'instructions sur votre ordinateur (le code de votre programme compilé).

Citation : Wikipédia
Un processus est défini par :
  • Un ensemble d'instructions à exécuter
  • Un espace mémoire pour les données de travail
  • Éventuellement, d'autres ressources, comme des descripteurs de fichiers, des ports réseaux, etc...

Si vous être sous Windows, vous pouvez accéder à la liste des processus via un gestionnaire des tâches (en appuyant simultanément sur les touches CTRL+ALT+SUPPR).
Sous linux, vous pouvez accéder à la liste des processus via un indicateur de performance.
Plus d'informations : ici.


Les threads



Un même processus peut se décomposer en plusieurs parties, qui vont s'exécuter simultanément en partageant les mêmes données en mémoire. Ces parties se nomment threads.
Du point de vue de l'utilisateur, les threads semblent se dérouler en parallèle.
Lorsqu'une fonction bloque par exemple un programme (comme la fonction recv), si celui-ci dispose d'une interface graphique, il sera inactif tant que la fonction le bloquera. Les threads nous permettront de régler ce problème.
Plus d'informations : ici.


Les mutex



Il est parfois nécessaire d'interdire momentanément certaines opérations d'un ou plusieurs threads : par exemple, si plusieurs threads sont amenés à lire une variable, faire des calculs avec puis la modifier en fonction du résultat de ces calculs, il ne faut pas qu'ils le fassent en même temps, sinon cela risque tous simplement de boguer.
Les mutex permettent donc d'éviter ces problèmes de synchronisation :) .
Plus d'informations : ici.


Pourquoi choisir la bibliothèque pthread ?



Le terme Pthread est une abréviation de "POSIX Threads".
POSIX est lui un acronyme de "Portable Operating System Interface for UniX".

J'ai donc choisi pthread car c'est une très bonne bibliothèque portable permettant de manipuler les threads, les processus et les mutex assez facilement ;) .

Installation de pthread

Sous Windows



Sous Windows, cette bibliothèque n'est pas installée par défaut, il va donc falloir le faire.
Nous allons dans un premier temps télécharger la bibliothèque puis l'installer et ensuite nous pourrons l'utiliser dans nos programmes.


Vous pouvez obtenir la bibliothèque pthread en cliquant sur le lien qui suit :
Pthread - Win32 - Version 2.8.0

Commencez par ouvrir l'exécutable et cliquez sur le bouton "Extract".
Ainsi, trois dossiers sont créés dans le répertoire de l'exécutable.
Le dossier "pthreads.2" contient les sources de la bibliothèque. Elles vous seront utiles dans le cas où vous devriez compiler vous même la bibliothèque.
Le dossier "Pre-built.2" contient les fichiers .h à inclure et les fichiers .lib à linker.
C'est donc ce dossier qui va nous intéresser, ouvrez le ;) .

Mettez les fichiers .lib/.a dans le dossier "lib" de votre compilateur et les headers (les fichiers .h) dans le dossier "include".

Petit rappel :
Pour ceux qui utilisent VC, prenez les fichiers ayant les extensions .lib.
Pour ceux qui utilisent Code::Blocks ou Dev-C++, prenez les fichiers ayant l'extension .a.

Une fois la bibliothèque pthread installée, nous allons linker les fichiers .lib/.a à notre projet et inclure les headers dans notre code. En fonction de votre IDE et du langage que vous avez choisi d'utiliser, le fichier linké ne sera pas le même :
Si vous faîtes du C, linkez le fichier "pthreadVC2.lib" pour VC et le fichier "libpthreadGCc.a" pour Code::Blocks et Dev-C++.
Sinon si vous faîtes du C++, linkez le fichier "pthreadVCE2.lib" pour VC et le fichier "libpthreadGCEc.a" pour Code::Blocks et Dev-C++.

Il en est de même pour les DLL ;) :
Si vous faîtes du C, prenez la DLL "pthreadVC2.dll" pour VC et la DLL "pthreadGC2.dll" pour Code::Blocks et Dev-C++.
Sinon si vous faîtes du C++, prenez la DLL "pthreadVCE2.dll" pour VC et la DLL "pthreadGCE2.dll" pour Code::Blocks et Dev-C++.
La DLL doit être mise dans le répertoire de votre projet.

Note : Vous pouvez mettre la DLL dans le répertoire "X:\WINDOWS\system32\" (ou X est le nom du disque dur contenant le dossier Windows). Ainsi, la DLL n'a plus besoin d'être dans le répertoire de vos projets :) sur votre ordinateur.

Nous allons ensuite inclure pthread comme ceci :

Code : C
1
#include <pthread.h>



Sous Linux



Sous linux la bibliothèque est déjà installée normalement, vous n'aurez donc pas besoin de le faire :) .
Toutefois sachez que les sources utilisant les threads nécessitent une édition de lien avec la librairie pthread :

Code : Console
gcc nomSource.c -lpthread -o nomExecutable

Les threads

Dans cette partie nous allons voir comment utiliser les threads avec la bibliothèque pthread.


Déclarer un thread



Pour pouvoir utiliser notre thread, nous allons tout d'abord déclarer une variable de type pthread_t comme il suit.

Code : C
1
pthread_t thread;


Créer un thread



Une fois que notre thread est déclaré, il va falloir le lier à une fonction de notre choix, la fonction désignée se déroulera ensuite en parallèle avec le reste de l'application. Pour réaliser cela nous allons utiliser la fonction pthread_create dont le prototype est donné ci-dessous.

Code : C
1
int pthread_create(pthread_t* thread, pthread_attr_t* attr, void*(*start_routine)(void*), void* arg);
  • En cas de succès la fonction renvoie 0. En cas d'erreur, la fonction renvoie un code d'erreur non nul.
  • L'argument thread correspond au thread qui va exécuter la fonction.
  • L'argument attr indique les attributs du thread, ce paramètre ne nous intéresse pas, nous mettrons donc celui-ci à NULL pour que les attributs par défaut soient utilisés.
  • L'argument start_routine correspond à la fonction à exécuter.
  • L'argument arg est un pointeur sur void qui sera passé à la fonction à exécuter. Si vous n'avez aucun paramètre à passer, mettez ce paramètre à NULL.
Toutefois, pour éviter des erreurs de compilation, la fonction exécutée par le thread créé devra toujours avoir le prototype suivant :

Code : C
1
void* ma_fonction(void* data);

Dans l'exemple qui suit, le thread thread va exécuter la fonction ma_fonction en parallèle avec comme paramètre l'entier valeur.

Code : C
1
pthread_create(&thread, NULL, ma_fonction, (void*)&valeur);

Pour le dernier argument, &valeur sera un int*, mais pour le transformer en void* nous ferons un simple cast :) .


Attendre la fin de l'exécution d'un thread



Une fois que notre thread est exécuté, il se peut que nous ayons besoin de savoir quand il se termine.
La fonction pthread_join va permettre d'attendre la fin du thread c'est à dire la fin de l'exécution de la fonction exécutée par celui-ci. Voici le prototype:

Code : C
1
int pthread_join(pthread_t thread, void **thread_return);
  • En cas de succès, la fonction renvoie 0. En cas d'erreur, la fonction renvoie un code d'erreur non nul.
  • L'argument thread correspond au thread à attendre.
  • L'argument thread_return est un pointeur sur la valeur de retour du thread.

Terminer le thread courant



Dans certain cas, il est possible que la fonction principale d'un thread ne se termine jamais, notamment lorsque l'on utilise une boucle infinie. Dans ce cas, on doit forcer la fin de du thread avec la fonction pthread_exit :

Code : C
1
void pthread_exit(void *retval);
  • La fonction ne renvoie rien.
  • L'argument retval est un pointeur sur void, il correspond à la valeur de retour du thread qui exécute la fonction (si cette fonction est utilisée dans la fonction principale du thread, cela équivaut à faire : "return retval;").

Terminer un thread



Dans de nombreux cas, il est possible que vous ayez à terminer un thread depuis un autre. Par exemple, lorsque vous utiliserez les threads avec les sockets, la fonction ci-dessous pourrait être utilisée pour expulser des clients (associés à un thread) depuis le thread principal.

Code : C
1
int pthread_cancel(pthread_t thread);
  • En cas de succès, la fonction renvoie 0. En cas d'erreur, la fonction renvoie un code d'erreur non nul.
  • L'argument thread correspond au thread à terminer.

La mémoire de tous les threads d'un même processus peut être partagée ou non. Dans le cas de la mémoire partagée, cela signifie que si on crée dynamiquement une variable dans un thread, elle peut être lue dans un autre (c'est le cas des variables globales par exemple). Dans le cas de la mémoire privée, les variables crées dans un thread ne peuvent pas être lue dans un autre (c'est le cas des objets locaux par exemple). Il faut donc bien surveiller la mémoire qui est allouée dans un thread car elle n'est pas automatiquement libérée à la fin du thread. Pensez donc à libérer la mémoire qui a été allouée dynamiquement au cours du thread avant sa destruction, si besoin est ^^ . Il faut aussi veiller à ne pas désallouer la mémoire qui sera utilisée par un autre thread durant la suite de l’exécution programme.


Un petit exemple :



Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// On inclue les fichiers standard et le header pthread.h
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
 
void* maFonction(void* data);
 
 
 
int main()
{
    int i;
    
    // On crée un thread
    pthread_t thread;
    
    // Permet d'exécuter le fonction maFonction en parallèle
    pthread_create(&thread, NULL, maFonction, NULL);
 
    // Affiche 50 fois 1
    for(i=0 ; i<50 ; i++)
        printf("1");
 
    // Attend la fin du thread créé
    pthread_join(thread, NULL);
    
    return 0;
}
 
 
void* maFonction(void* data)
{
    int i;
    
    // Affiche 50 fois 2
    for(i=0 ; i<50 ; i++)
        printf("2");
    
    return NULL;
}

Le résultat donné est le suivant :

Code : Console
1111111111222222222222222222222222222222222111111111111111111111111111111111112222222222222222211111

Comme vous pouvez le voir, l'utilisation des threads permet d'exécuter une fonction en parallèle.

Mais pourquoi les nombres s'affichent par paquets ? Si les fonctions se lançaient en parallèle, je devrais obtenir 12121212...

Si les nombres s'affichent par paquet, c'est à cause de votre système d'exploitation et de votre processeur.
En effet, avec un processeur mono-coeur, la fonction principale (main) et la fonction exécuté par le thread créé (maFonction) ne se lance pas tout à fait en parallèle car votre OS va attribuer un temps d'exécution pour chaque thread.
Votre OS va, par exemple, exécuter le code du thread n°1 de votre processus pendant 8 ms, puis va exécuter le code du thread n°2 du même processus aussi pendant 8 ms, etc. Il en est de même pour le fonctionnement des processus ;) .
Ainsi, comme chaque thread est exécuté à tour de rôle pendant un temps très court, vous avez l'impression qu'ils se déroulent en parallèle ^^ . On appel ça l’ordonnancement des thread.

Si vous avez un processeur multi-coeurs cela n'est plus vraiment valable car les deux threads peuvent réellement se dérouler en parallèle, et c'est tout l'avantage des threads :) .
Par exemple, si vous disposez d'un processeur qui dispose de deux coeurs ou plus et que votre application lance deux threads simultanément, votre application sera théoriquement jusqu'à deux fois plus rapide. De même avec quatre coeurs ou plus et quatre threads votre application sera théoriquement jusqu'à quatre fois plus rapide, etc. Par contre, avec deux coeurs et quatre threads votre application sera jusqu'à deux fois plus rapide et non pas quatre en théorie :p .

Ces dernières années, le nombre de coeurs dans les processeurs ne cesse de croître et donc il est de plus en plus intéressant de mettre en place des solutions parallèles efficaces pour accélérer nos programmes.
Vous avez surement observé que les thread et les mutex ont un fonctionnement est assez simple à assimiler. Cependant, selon l'utilisation que l'on en fait, cela peut très vite devenir une horreur. Il faut donc être assez rigoureux quand on met en place des threads et surtout particulièrement lorsque l'on utilise des mutex pour éviter par exemple les problèmes inter-blocage ;) .
Chapitre précédent Sommaire Chapitre suivant

Partager

30 commentaires pour "Les threads et les mutex"
Note moyenne : 3.54 / 4 (79 votes)
Pseudo Commentaire
Hors ligne nath # Posté le 14/04/2011 à 21:30:50
yeeah, rastafari!!!!
Avatar

Court mais intense !
Tu m'as ouvert la voie :)
Hors ligne Genroa # Posté le 07/06/2011 à 19:29:07

Superbe! Un peu complexe dès qu'on veut s'en servir correctement pour des fonctionnements complexes, mais sinon tout est bon.
Un petit exemple de type tchat mutli clients serait le bienvenu :)
18/20! :D
Hors ligne Neckara # Posté le 27/11/2011 à 13:48:23

Edit : J'ai rencontré un problème lors de l'installation de la librairie et je pensais avoir trouvé une solution (et j'avais donc fait ce post dans le but d'aider les personnes qui en lisant ce tutos rencontreraient les mêmes problèmes)mais celle-ci semble ne pas fonctionner, elle provoque l'exécution sans recompilation (d'où mon erreur de penser que le problème était résolu).
Pardonnez moi pour ce post.
Hors ligne Accro # Posté le 22/02/2012 à 16:51:13
Avatar

Études : Epitech Paris

Bon tuto, cependant une remarque : dans le titre tu parle de mutex mais tu n'en montre pas l'utilisation dans la lib phtread
Hors ligne Hakzman # Posté le 23/03/2012 à 12:24:14
Ne jamais dire jamais !
Avatar

Avis : Très bon

Très bien expliqué.
Merci

Il y a 10 sortes de gens: ceux qui comptent en decimal, et ceux qui comptent en binaire.
Me faire détester est mon vice.Déplaire est mon plaisir.
J'aime que l'on me haïsse.

Image utilisateur
 

Voir tous les commentaires