[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Non-Officiels
> Programmation
> C
> Les sockets > Les sockets entre deux applications > Les threads et les mutex
> Lecture du tutoriel
Les threads et les mutex
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
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

.
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édiaUn 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 permettrons 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 bugger.
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

.
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 même 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
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 |
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 déclarer une variable de type
pthread_t.
Code : C
Créer un thread
La fonction
pthread_create va nous permettre de créer un thread et d'exécuter une fonction en parallèle. Voici son implémentation:
Code : C1 | 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 avoir le prototype suivant :
Code : C1 | void* fonction(void* data);
|
Dans l'exemple qui suit, le thread
thread va exécuter la fonction
ma_fonction avec comme paramètre l'entier
valeur.
Code : C1 | 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 cast

.
Attendre la fin de l'exécution d'un thread
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 : C1 | int pthread_join(pthread_t th, 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 th correspond au thread à attendre.
- L'argument thread_return est un pointeur sur la valeur de retour du thread.
Terminer un thread
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 : C1 | 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).
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 | // On inclue les fichiers standard et le header pthread.h
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
void* maFonction(void*);
int main()
{
pthread_t thread;// On crée un thread
pthread_create (&thread, NULL, maFonction, NULL);// Permet d'exécuter le fonction maFonction en parallèle
// Affiche 50 fois 1
for(int i=0 ; i<50 ; i++)
printf("1");
// Attend la fin tu thread créé
pthread_join(thread, NULL);
return 0;
}
void* maFonction(void*)
{
// Affiche 50 fois 2
for(int i=0 ; i<50 ; i++)
printf("2");
}
|
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 OS.
En effet, 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 ^^.