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
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 | 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 | 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 | 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 | 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 | 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 | 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

.
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.