1. Introduction générale
Un thread, ou fil d’exécution, est une unité d’exécution à l’intérieur d’un processus.
Un processus classique ne possède qu’un seul thread principal. Un processus multithreadé possède plusieurs threads qui peuvent exécuter différentes parties du même programme de manière concurrente.
Les threads POSIX sont généralement appelés Pthreads. Ils sont définis par la norme POSIX et sont très utilisés sous Linux et les systèmes de type UNIX.
Le principal intérêt des threads est de permettre à une même application d’effectuer plusieurs tâches en parallèle ou de manière concurrente, par exemple :
- un serveur qui gère plusieurs clients ;
- une application graphique qui continue à répondre pendant un calcul ;
- un programme qui effectue plusieurs traitements simultanés ;
- un logiciel qui sépare la réception réseau, le traitement et l’affichage ;
- un moteur de jeu qui sépare le rendu, la physique, l’audio et le réseau.
2. Processus monothreadé et processus multithreadé
Dans un processus monothreadé, un seul flux d’exécution existe.
Dans un processus multithreadé, plusieurs flux d’exécution partagent le même espace mémoire.
Tous les threads d’un même processus :
- exécutent le même programme ;
- partagent les variables globales ;
- partagent les données statiques ;
- partagent le tas ;
- partagent les bibliothèques chargées ;
- partagent les descripteurs de fichiers ouverts ;
- partagent une grande partie de l’environnement du processus.
- sa propre pile ;
- son propre identifiant ;
- son propre masque de signaux ;
- ses propres données spécifiques au thread ;
- sa propre valeur de errno ;
- certains attributs d’ordonnancement ;
- sa propre valeur de retour.
Un processus contient généralement plusieurs zones mémoire :
- le segment de code ;
- les données initialisées ;
- les données non initialisées, souvent appelées BSS ;
- le tas ;
- les bibliothèques partagées ;
- une pile pour le thread principal ;
- une pile distincte pour chaque thread supplémentaire.
Exemple :
Code: Select all
int compteur_global = 0;
void *threadFunc(void *arg)
{
compteur_global++;
return NULL;
}
Cela facilite le partage d’informations, mais crée aussi des risques de concurrence. Si plusieurs threads modifient la même donnée au même moment, le résultat peut devenir incohérent.
Ce problème sera traité avec des mécanismes de synchronisation comme :
- les mutex ;
- les variables de condition ;
- les sémaphores ;
- les verrous en lecture-écriture ;
- les opérations atomiques.
Un serveur réseau multithreadé peut fonctionner ainsi :
- le thread principal attend une connexion ;
- un client se connecte ;
- le serveur crée un nouveau thread ;
- ce nouveau thread gère le client ;
- le thread principal retourne immédiatement à l’attente de nouvelles connexions.
Code: Select all
while (1)
{
client = accepter_client();
pthread_create(
&thread,
NULL,
gerer_client,
client
);
}
5. Threads et processus : principales différences
5.1 Partage des données
Les threads partagent naturellement la mémoire du processus.
Les processus séparés ont chacun leur propre espace mémoire. Pour échanger des données, ils doivent utiliser des mécanismes de communication interprocessus, par exemple :
- pipes ;
- sockets ;
- files de messages ;
- mémoire partagée ;
- signaux ;
- fichiers.
Créer un thread est généralement plus léger que créer un processus.
La création d’un processus avec fork() implique la création d’un nouveau contexte de processus et la gestion d’un nouvel espace mémoire virtuel.
Même avec la technique copy-on-write, certaines structures doivent être créées et maintenues.
La création d’un thread ne duplique pas tout l’espace mémoire du processus.
5.3 Changement de contexte
Passer d’un thread à un autre dans un même processus est souvent moins coûteux que passer d’un processus à un autre.
Les threads partagent déjà le même espace d’adressage.
5.4 Isolation
Les processus sont mieux isolés.
Une erreur mémoire dans un processus ne corrompt normalement pas directement la mémoire d’un autre processus.
Dans un processus multithreadé, une erreur dans un thread peut affecter tous les autres threads.
Exemples :
- écriture via un pointeur invalide ;
- corruption du tas ;
- dépassement de pile ;
- modification incorrecte d’une variable globale ;
- fermeture accidentelle d’un descripteur partagé.
Les threads d’un même processus partagent notamment :
- l’identifiant du processus ;
- l’identifiant du processus parent ;
- l’identifiant du groupe de processus ;
- l’identifiant de session ;
- le terminal de contrôle ;
- les identifiants utilisateur et groupe ;
- les descripteurs de fichiers ouverts ;
- les verrous d’enregistrement de fichiers ;
- les dispositions des signaux ;
- le répertoire de travail courant ;
- le répertoire racine ;
- le masque de création de fichiers ;
- les limites de ressources ;
- certaines temporisations ;
- les valeurs nice et les informations générales du processus ;
- le temps CPU global du processus.
Exemple :
Code: Select all
close(fd);
7. Attributs propres à chaque thread
Chaque thread possède notamment :
- un identifiant de type pthread_t ;
- une pile privée ;
- un masque de signaux ;
- des données spécifiques au thread ;
- une valeur errno indépendante ;
- une politique d’ordonnancement ;
- une priorité d’ordonnancement ;
- une affinité CPU ;
- certaines capacités Linux ;
- des informations de pile ;
- une valeur de retour.
Techniquement, un thread peut donc accéder à la pile d’un autre thread s’il connaît son adresse. Cela est dangereux et doit être évité.
8. Inclusion de l’en-tête Pthreads
Pour utiliser les fonctions Pthreads, il faut inclure :
Code: Select all
#include <pthread.h>
Sous GCC ou Clang, il faut compiler et lier avec l’option :
Code: Select all
-pthread
Code: Select all
gcc programme.c -o programme -pthread
L’option -pthread peut à la fois :
- activer les macros nécessaires à la compilation ;
- configurer correctement le code pour le multithreading ;
- lier la bibliothèque Pthreads.
L’API Pthreads définit plusieurs types opaques.
Un type opaque est un type dont le programme ne doit pas supposer la représentation interne.
Principaux types :
- pthread_t : identifiant d’un thread ;
- pthread_attr_t : attributs de création d’un thread ;
- pthread_mutex_t : mutex ;
- pthread_mutexattr_t : attributs d’un mutex ;
- pthread_cond_t : variable de condition ;
- pthread_condattr_t : attributs d’une variable de condition ;
- pthread_key_t : clé pour les données spécifiques à un thread ;
- pthread_once_t : contrôle d’une initialisation exécutée une seule fois.
Selon l’implémentation, il peut être :
- un entier ;
- un entier non signé ;
- un pointeur ;
- une structure ;
- un autre type interne.
Les fonctions UNIX classiques retournent souvent :
- -1 en cas d’erreur ;
- et placent le code d’erreur dans errno.
En général :
- elles retournent 0 en cas de succès ;
- elles retournent directement un numéro d’erreur positif en cas d’échec.
Code: Select all
int s;
s = pthread_create(&thread, NULL, threadFunc, NULL);
if (s != 0)
{
fprintf(stderr, "pthread_create a échoué : %s\n",
strerror(s));
}
Code: Select all
if (pthread_create(...) == -1)
12. errno dans un programme multithreadé
Dans un programme monothreadé ancien, errno pouvait être une simple variable globale.
Dans un programme multithreadé, cela provoquerait des conflits.
Exemple :
- le thread A appelle une fonction qui fixe errno ;
- le thread B appelle immédiatement une autre fonction ;
- le thread B modifie errno ;
- le thread A lit une valeur qui ne correspond plus à son erreur.
L’implémentation utilise souvent une macro ou une fonction interne qui retourne un emplacement spécifique au thread.
Le programme peut donc utiliser normalement :
Code: Select all
if (open("fichier.txt", O_RDONLY) == -1)
{
perror("open");
}
13. Création d’un thread avec pthread_create()
Prototype :
Code: Select all
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg
);
Le paramètre thread reçoit l’identifiant du nouveau thread.
Exemple :
Code: Select all
pthread_t tid;
Code: Select all
pthread_create(&tid, NULL, threadFunc, NULL);
Le paramètre attr permet de définir les attributs du thread.
Si sa valeur est NULL, les attributs par défaut sont utilisés.
Exemple :
Code: Select all
pthread_create(&tid, NULL, threadFunc, NULL);
Il s’agit de la fonction exécutée par le nouveau thread.
Cette fonction doit respecter la forme :
Code: Select all
void *fonction_thread(void *arg);
Code: Select all
void *threadFunc(void *arg)
{
printf("Bonjour depuis le thread\n");
return NULL;
}
Le paramètre arg est transmis à la fonction du thread.
Il s’agit d’un pointeur générique de type void *.
On peut transmettre :
- un pointeur vers un entier ;
- une chaîne de caractères ;
- un pointeur vers une structure ;
- un objet alloué dynamiquement.
Code: Select all
void *threadFunc(void *arg)
{
char *texte = arg;
printf("%s\n", texte);
return NULL;
}
int main(void)
{
pthread_t tid;
char message[] = "Bonjour";
pthread_create(&tid, NULL, threadFunc, message);
pthread_join(tid, NULL);
return 0;
}
Une fonction de thread ne reçoit qu’un seul argument de type void *.
Pour transmettre plusieurs valeurs, il faut généralement utiliser une structure.
Exemple :
Code: Select all
struct ThreadArgs
{
int id;
const char *nom;
double valeur;
};
void *threadFunc(void *arg)
{
struct ThreadArgs *args = arg;
printf("ID : %d\n", args->id);
printf("Nom : %s\n", args->nom);
printf("Valeur : %f\n", args->valeur);
return NULL;
}
Code: Select all
struct ThreadArgs args =
{
.id = 1,
.nom = "Worker",
.valeur = 42.5
};
pthread_create(&tid, NULL, threadFunc, &args);
15. Danger lié à la durée de vie des arguments
Exemple dangereux :
Code: Select all
void creer_thread(void)
{
pthread_t tid;
int valeur = 42;
pthread_create(&tid, NULL, threadFunc, &valeur);
}
Lorsque la fonction se termine, cette variable n’est plus valide.
Le thread peut alors accéder à une adresse contenant des données invalides.
Solutions possibles :
- attendre le thread avec pthread_join() avant de quitter la fonction ;
- allouer les arguments dynamiquement avec malloc() ;
- utiliser une structure ayant une durée de vie suffisante ;
- copier immédiatement les données dans le thread.
Code: Select all
int *valeur = malloc(sizeof(*valeur));
if (valeur == NULL)
{
perror("malloc");
exit(EXIT_FAILURE);
}
*valeur = 42;
pthread_create(&tid, NULL, threadFunc, valeur);
Code: Select all
void *threadFunc(void *arg)
{
int valeur = *(int *)arg;
free(arg);
printf("%d\n", valeur);
return NULL;
}
Après pthread_create(), l’ordre d’exécution n’est pas garanti.
Le nouveau thread peut s’exécuter :
- avant que pthread_create() ne retourne ;
- juste après ;
- plus tard ;
- avant ou après une instruction du thread principal.
Code: Select all
pthread_create(&tid, NULL, threadFunc, NULL);
printf("Message du thread principal\n");
Il ne faut jamais dépendre de l’ordre d’exécution sans mécanisme de synchronisation.
17. Terminaison normale d’un thread
Un thread peut se terminer en retournant depuis sa fonction.
Exemple :
Code: Select all
void *threadFunc(void *arg)
{
printf("Travail terminé\n");
return NULL;
}
Code: Select all
pthread_exit(NULL);
Prototype :
Code: Select all
void pthread_exit(void *retval);
Elle ne termine pas automatiquement tout le processus.
Exemple :
Code: Select all
void *threadFunc(void *arg)
{
int *resultat = malloc(sizeof(*resultat));
if (resultat == NULL)
{
pthread_exit(NULL);
}
*resultat = 123;
pthread_exit(resultat);
}
19. Différence entre pthread_exit() et exit()
pthread_exit() termine uniquement le thread appelant.
exit() termine le processus entier.
Donc :
Code: Select all
pthread_exit(NULL);
Alors que :
Code: Select all
exit(EXIT_SUCCESS);
Le retour de la fonction main() équivaut également à terminer le processus entier.
20. Cas où tous les threads sont terminés
Tous les threads du processus sont terminés si :
- la fonction main() effectue un return ;
- un thread appelle exit() ;
- un signal fatal termine le processus ;
- le processus est tué ;
- une erreur fatale provoque la terminaison générale.
Code: Select all
pthread_exit(NULL);
Code: Select all
int main(void)
{
pthread_t tid;
pthread_create(&tid, NULL, threadFunc, NULL);
pthread_exit(NULL);
}
21. Identifiant du thread courant avec pthread_self()
Prototype :
Code: Select all
pthread_t pthread_self(void);
Exemple :
Code: Select all
pthread_t moi = pthread_self();
- identifier le thread courant ;
- comparer son identifiant ;
- se détacher soi-même ;
- associer des informations à un thread ;
- journaliser l’exécution.
Prototype :
Code: Select all
int pthread_equal(pthread_t t1, pthread_t t2);
Exemple :
Code: Select all
if (pthread_equal(tid, pthread_self()))
{
printf("Le thread courant est tid\n");
}
Code: Select all
if (tid == pthread_self())
23. Identifiants Pthreads et identifiants Linux
L’identifiant retourné par pthread_self() est un identifiant Pthreads.
Il ne correspond pas nécessairement à l’identifiant de thread du noyau Linux.
Sous Linux, chaque thread possède aussi un identifiant noyau, souvent appelé TID.
Les deux notions ne doivent pas être confondues :
- pthread_t est utilisé par l’API Pthreads ;
- le TID Linux est utilisé par certaines interfaces spécifiques au noyau.
24. Attente d’un thread avec pthread_join()
Prototype :
Code: Select all
int pthread_join(
pthread_t thread,
void **retval
);
Si le thread est déjà terminé, elle retourne immédiatement.
Le premier paramètre est l’identifiant du thread à attendre.
Le second paramètre permet de récupérer sa valeur de retour.
Exemple simple :
Code: Select all
pthread_join(tid, NULL);
25. Récupération de la valeur de retour
Exemple :
Code: Select all
void *threadFunc(void *arg)
{
int *resultat = malloc(sizeof(*resultat));
if (resultat == NULL)
return NULL;
*resultat = 42;
return resultat;
}
Code: Select all
void *retour;
pthread_join(tid, &retour);
if (retour != NULL)
{
int *valeur = retour;
printf("Résultat : %d\n", *valeur);
free(valeur);
}
- le return de la fonction du thread ;
- ou pthread_exit().
Un thread joignable qui se termine conserve certaines informations jusqu’à ce qu’un autre thread appelle pthread_join().
Il peut être comparé à un thread zombie.
Le code du thread ne s’exécute plus, mais certaines ressources restent réservées.
Il faut donc :
- soit rejoindre le thread avec pthread_join() ;
- soit le détacher.
Pour les processus, wait() et waitpid() utilisent une relation parent-enfant.
Pour les threads :
- il n’existe pas de relation parent-enfant stricte ;
- n’importe quel thread peut rejoindre un autre thread du même processus ;
- pthread_join() attend un thread précis ;
- un thread ne doit normalement être rejoint qu’une fois ;
- plusieurs threads ne doivent pas tenter de rejoindre simultanément le même thread.
Un thread ne doit pas tenter de se rejoindre lui-même.
Exemple incorrect :
Code: Select all
pthread_join(pthread_self(), NULL);
Cela provoque une situation de blocage ou une erreur selon l’implémentation.
Pour éviter cela :
Code: Select all
if (!pthread_equal(tid, pthread_self()))
{
pthread_join(tid, NULL);
}
Code: Select all
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void *threadFunc(void *arg)
{
const char *texte = arg;
printf("%s\n", texte);
return (void *)strlen(texte);
}
int main(void)
{
pthread_t tid;
void *retour;
int s;
s = pthread_create(
&tid,
NULL,
threadFunc,
"Hello world"
);
if (s != 0)
{
fprintf(stderr,
"pthread_create : %s\n",
strerror(s));
exit(EXIT_FAILURE);
}
printf("Message from main()\n");
s = pthread_join(tid, &retour);
if (s != 0)
{
fprintf(stderr,
"pthread_join : %s\n",
strerror(s));
exit(EXIT_FAILURE);
}
printf("Thread returned %ld\n", (long)retour);
return EXIT_SUCCESS;
}
Code: Select all
Message from main()
Code: Select all
Hello world
30. Threads joignables et threads détachés
Par défaut, un thread est joignable.
Cela signifie qu’un autre thread doit appeler pthread_join() pour récupérer sa terminaison et libérer ses ressources associées.
Un thread détaché libère automatiquement ses ressources lorsqu’il se termine.
Il n’est alors plus possible de le rejoindre.
31. Détachement avec pthread_detach()
Prototype :
Code: Select all
int pthread_detach(pthread_t thread);
Code: Select all
pthread_detach(tid);
- le thread devient détaché ;
- ses ressources seront libérées automatiquement ;
- pthread_join() ne doit plus être utilisé sur ce thread.
Code: Select all
pthread_detach(pthread_self());
Code: Select all
void *threadFunc(void *arg)
{
pthread_detach(pthread_self());
printf("Thread détaché\n");
return NULL;
}
Une fois détaché, un thread ne peut pas redevenir joignable.
Il faut choisir :
- thread joignable si la valeur de retour ou la confirmation de fin est nécessaire ;
- thread détaché si personne n’a besoin de l’attendre.
- gestion indépendante d’un client réseau ;
- journalisation ;
- nettoyage en arrière-plan ;
- traitement ne nécessitant aucun résultat.
On peut créer un thread directement détaché grâce à un objet d’attributs.
Types et fonctions utilisées :
- pthread_attr_t ;
- pthread_attr_init() ;
- pthread_attr_setdetachstate() ;
- pthread_create() ;
- pthread_attr_destroy().
Prototype :
Code: Select all
int pthread_attr_init(pthread_attr_t *attr);
Exemple :
Code: Select all
pthread_attr_t attr;
int s;
s = pthread_attr_init(&attr);
35. Définition de l’état détaché
Prototype :
Code: Select all
int pthread_attr_setdetachstate(
pthread_attr_t *attr,
int detachstate
);
- PTHREAD_CREATE_JOINABLE ;
- PTHREAD_CREATE_DETACHED.
Code: Select all
pthread_attr_setdetachstate(
&attr,
PTHREAD_CREATE_DETACHED
);
Prototype :
Code: Select all
int pthread_attr_destroy(pthread_attr_t *attr);
Exemple :
Code: Select all
pthread_attr_destroy(&attr);
Elle libère uniquement les ressources internes associées à l’objet pthread_attr_t.
37. Exemple complet de thread créé détaché
Code: Select all
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void *threadFunc(void *arg)
{
printf("Thread détaché en cours\n");
return NULL;
}
int main(void)
{
pthread_t tid;
pthread_attr_t attr;
int s;
s = pthread_attr_init(&attr);
if (s != 0)
{
fprintf(stderr,
"pthread_attr_init : %s\n",
strerror(s));
exit(EXIT_FAILURE);
}
s = pthread_attr_setdetachstate(
&attr,
PTHREAD_CREATE_DETACHED
);
if (s != 0)
{
fprintf(stderr,
"pthread_attr_setdetachstate : %s\n",
strerror(s));
exit(EXIT_FAILURE);
}
s = pthread_create(
&tid,
&attr,
threadFunc,
NULL
);
if (s != 0)
{
fprintf(stderr,
"pthread_create : %s\n",
strerror(s));
exit(EXIT_FAILURE);
}
s = pthread_attr_destroy(&attr);
if (s != 0)
{
fprintf(stderr,
"pthread_attr_destroy : %s\n",
strerror(s));
exit(EXIT_FAILURE);
}
pthread_exit(NULL);
}
Utiliser un thread joignable lorsque :
- on veut connaître sa fin ;
- on veut récupérer une valeur de retour ;
- on veut synchroniser le programme ;
- on doit attendre que son travail soit terminé ;
- on doit garantir la libération d’une ressource après sa fin.
- le résultat n’est pas nécessaire ;
- aucun thread n’a besoin de l’attendre ;
- la tâche est complètement autonome ;
- on veut une récupération automatique des ressources.
Le multithreading introduit plusieurs catégories de problèmes.
39.1 Conditions de course
Une condition de course apparaît lorsque le résultat dépend de l’ordre imprévisible d’exécution des threads.
Exemple :
Code: Select all
compteur++;
- lecture du compteur ;
- incrémentation ;
- écriture du résultat.
39.2 Interblocages
Un interblocage se produit lorsque plusieurs threads s’attendent mutuellement.
Exemple :
- le thread A possède le verrou 1 et attend le verrou 2 ;
- le thread B possède le verrou 2 et attend le verrou 1.
Un thread peut ne jamais obtenir les ressources nécessaires parce que d’autres threads sont toujours favorisés.
39.4 Corruption de mémoire
Tous les threads partagent la mémoire.
Une erreur dans un seul thread peut détruire les données de tous les autres.
39.5 Ordre d’exécution imprévisible
Le programme ne doit pas supposer qu’un thread commencera ou terminera avant un autre sans synchronisation explicite.
40. Threads et signaux
La gestion des signaux devient plus complexe dans une application multithreadée.
Certains éléments sont partagés au niveau du processus, tandis que le masque de signaux est propre à chaque thread.
En pratique, il est souvent recommandé de :
- bloquer certains signaux dans les threads ;
- désigner un thread spécifique pour les traiter ;
- utiliser des fonctions adaptées aux programmes multithreadés.
Chaque thread possède sa propre pile.
Créer un grand nombre de threads peut donc consommer beaucoup d’espace mémoire virtuel.
Sur un système 32 bits, l’espace d’adressage est limité.
Même si la mémoire physique n’est pas entièrement utilisée, les réservations de piles peuvent empêcher la création de nouveaux threads.
Il faut éviter de créer un nombre illimité de threads.
Une architecture avec pool de threads est souvent préférable.
42. Tableau récapitulatif des API principales
42. Tableau récapitulatif des API principales
- pthread_create() : crée un nouveau thread.
- pthread_exit() : termine uniquement le thread appelant.
- pthread_self() : retourne l’identifiant du thread courant.
- pthread_equal() : compare deux identifiants de threads.
- pthread_join() : attend la fin d’un thread joignable et récupère ses ressources.
- pthread_detach() : détache un thread afin que ses ressources soient libérées automatiquement à sa terminaison.
- pthread_attr_init() : initialise un objet contenant les attributs de création d’un thread.
- pthread_attr_setdetachstate() : définit si le thread sera joignable ou détaché.
- pthread_attr_destroy() : détruit l’objet d’attributs après son utilisation.
Code: Select all
#include <pthread.h>
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg
);
void pthread_exit(void *retval);
pthread_t pthread_self(void);
int pthread_equal(
pthread_t t1,
pthread_t t2
);
int pthread_join(
pthread_t thread,
void **retval
);
int pthread_detach(
pthread_t thread
);
int pthread_attr_init(
pthread_attr_t *attr
);
int pthread_attr_setdetachstate(
pthread_attr_t *attr,
int detachstate
);
int pthread_attr_destroy(
pthread_attr_t *attr
);
- Toujours vérifier les valeurs de retour des fonctions Pthreads.
- Utiliser strerror(code_erreur) avec les erreurs Pthreads.
- Compiler avec -pthread.
- Ne pas comparer directement deux pthread_t avec ==.
- Utiliser pthread_equal().
- Ne pas transmettre l’adresse d’une variable locale qui disparaîtra trop tôt.
- Choisir clairement entre thread joignable et thread détaché.
- Toujours joindre ou détacher les threads.
- Ne pas tenter de joindre un thread détaché.
- Ne pas joindre deux fois le même thread.
- Ne pas appeler pthread_join() sur le thread courant.
- Ne pas supposer l’ordre d’exécution des threads.
- Protéger les données partagées avec des mécanismes de synchronisation.
- Éviter de créer un nombre illimité de threads.
- Faire attention aux descripteurs de fichiers partagés.
- Faire attention à la durée de vie des valeurs de retour.
Code: Select all
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct ThreadArgs
{
int id;
const char *message;
};
struct ThreadResult
{
int id;
size_t longueur;
};
static void *threadFunc(void *arg)
{
struct ThreadArgs *args = arg;
struct ThreadResult *result =
malloc(sizeof(*result));
if (result == NULL)
{
return NULL;
}
printf(
"Thread %d : %s\n",
args->id,
args->message
);
result->id = args->id;
result->longueur = strlen(args->message);
return result;
}
int main(void)
{
pthread_t tid;
struct ThreadArgs args;
struct ThreadResult *result;
void *retour;
int s;
args.id = 1;
args.message = "Bonjour depuis Pthreads";
s = pthread_create(
&tid,
NULL,
threadFunc,
&args
);
if (s != 0)
{
fprintf(
stderr,
"pthread_create : %s\n",
strerror(s)
);
return EXIT_FAILURE;
}
s = pthread_join(tid, &retour);
if (s != 0)
{
fprintf(
stderr,
"pthread_join : %s\n",
strerror(s)
);
return EXIT_FAILURE;
}
result = retour;
if (result != NULL)
{
printf(
"Résultat du thread %d : longueur = %zu\n",
result->id,
result->longueur
);
free(result);
}
return EXIT_SUCCESS;
}
Code: Select all
gcc programme.c -o programme -pthread
Les threads POSIX permettent à un processus d’exécuter plusieurs tâches de manière concurrente.
Ils partagent l’espace mémoire, les variables globales, le tas et de nombreux attributs du processus, mais chaque thread possède sa propre pile et certains attributs individuels.
La création se fait avec :
Code: Select all
pthread_create()
Code: Select all
return
Code: Select all
pthread_exit()
Code: Select all
pthread_join()
Code: Select all
pthread_detach()
Code: Select all
pthread_attr_init()
pthread_attr_setdetachstate()
pthread_attr_destroy()
Une mauvaise synchronisation, une erreur mémoire ou une mauvaise gestion de la durée de vie des données peut affecter tout le processus.
La maîtrise des threads repose donc sur trois principes fondamentaux :
- bien gérer leur cycle de vie ;
- bien gérer les données partagées ;
- ne jamais supposer un ordre d’exécution sans synchronisation.
