Les threads
Processus légers POSIX : pthread_create, pthread_join, pthread_exit, pthread_detach, mutex et sections critiques. Compilation avec -lpthread.
📄 Ressources du cours
🎥 Vidéos recommandées
-
🇫🇷
Processus et Threads
Tarik3 Med3 · 48:30 · 28k vues — AboutPressCopyrightContact usCreatorsAdvertiseDevelopersTermsPrivacyPolicy & SafetyHow YouTube worksTest new features · © 2024 Google LLC
-
🇫🇷
La programmation système sous Linux: Semaphore et ordonnancement des threads
Walid SASSI · 10:53 · 11k vues — AboutPressCopyrightContact usCreatorsAdvertiseDevelopersTermsPrivacyPolicy & SafetyHow YouTube worksTest new featuresNFL Sunday Ticket · © 2024 Google LLC
-
🇬🇧
Mutex Introduction (pthreads) | C Programming Tutorial
Portfolio Courses · 10:19 · 18k vues — How to use a mutex to prevent race conditions in a multi-threaded C program using the POSIX pthread library. Source code: https://github.com/portfoliocourses...
1. Pourquoi les threads ?
On sait déjà implémenter des algorithmes parallèles avec ce que l'on a vu jusqu'ici (les IPC), mais c'est lourd. Le fork reste un appel système coûteux et la communication entre processus est lente. Les threads (processus légers) viennent corriger ces défauts.
- Un thread est une « partie » d'un processus.
- Un processus est l'exécution d'un ensemble (≥ 1) de threads.
- Chaque thread est associée à une pile d'exécution indépendante.
2. Thread vs Processus
Ce que les threads partagent (et ce qu'ils ne partagent pas)
Variables globales · variables statiques locales · descripteurs de fichiers ouverts · PID · PPID · utilisateurs propriétaires · handlers de signaux · répertoire courant · masque de fichiers.
Les identifiants de threads · la pile d'exécution · le masque des signaux.
Tableau comparatif
| Critère | Processus (fork) | Thread (pthread) |
|---|---|---|
| Espace d'adressage | Indépendant (un par processus) | Partagé dans le processus |
| Pile d'exécution | Une par processus | Une par thread |
| PID / PPID | Propres à chaque processus | Communs (tous les threads partagent le PID) |
| Variables globales / statiques | Copies indépendantes | Partagées |
| Descripteurs de fichiers | Hérités à la création | Partagés |
| Création | Lourde (appel système) | Légère (moins de données à recopier) |
| Changement de contexte | Lent / coûteux | Plus facile / rapide |
| Communication | IPC (tubes, signaux, mémoire partagée…) | Mémoire partagée directe |
| Synchronisation | Gérée par le système | À la charge du programmeur |
| Attente de fin | wait / waitpid | pthread_join |
Avantages / inconvénients des threads
- Partage de mémoire = mécanisme rapide de communication inter-thread.
- Plus léger : moins de données système à recopier.
- Plus rapide : context-switch facilité.
- Les threads utilisent les mêmes copies des librairies.
- Il faut gérer la synchronisation (mutex, sémaphores…).
- Mauvaise synchronisation ⇒ comportement aléatoire : bugs, segfaults, interblocage.
3. Modèle mémoire : un processus, plusieurs threads
Au départ, le processus est constitué d'un unique thread (thread principal) — celui qui exécute la fonction main. La création d'un processus crée un thread natif. Si le thread principal se termine, tous les autres threads du processus se terminent aussi (sauf si l'on utilise pthread_exit dans le main).
- Dans les bibliothèques utilisateur : contrôlés par l'utilisateur — POSIX Pthreads, Java threads.
- Dans le noyau du SE : contrôlés par le noyau — Windows XP/2000, Solaris, Linux, UNIX, Mac OS X.
- Et solutions mixtes.
4. Primitives POSIX (récap)
| Appel | Description |
|---|---|
pthread_create | Créer un nouveau thread (fil d'exécution) |
pthread_exit | Terminer le thread appelant |
pthread_join | Attendre la fin d'un autre thread |
pthread_mutex_init | Créer un mutex |
pthread_mutex_destroy | Détruire un mutex |
pthread_mutex_lock | Verrouiller un mutex |
pthread_mutex_unlock | Relâcher un mutex |
gcc prog.c -o prog -lpthread
Et inclure <pthread.h>.
5. Créer un thread — pthread_create()
#include <pthread.h>
int pthread_create(pthread_t *tid,
const pthread_attr_t *attr,
void *(*func)(void *),
void *arg);
pthread_t *tidPointeur vers l'adresse mémoire qui contient le TID (Thread Identifier) du pthread créé.
const pthread_attr_t *attrAttributs du pthread (taille de la pile, priorité, ressources système…).
attr = NULL ⇒ attributs par défaut (thread joignable, non détaché, ordonnancement normal — pas temps-réel).
void *(*func)(void *)La fonction à exécuter par le pthread.
void *argLes arguments passés à la fonction
func.
- Un pthread n'a pas de PID propre : tous les threads partagent le PID du processus.
- L'identifiant est un objet de type
pthread_t: la norme ne dit pas ce qu'il contient (objet opaque). - L'appel renvoie 0 en cas de succès, sinon une valeur non nulle identifiant l'erreur.
Erreurs possibles
| Code | Signification |
|---|---|
EAGAIN | Ressources insuffisantes ou limite de threads atteinte |
EINVAL | Paramètres invalides dans attr |
EPERM | Permissions insuffisantes pour la politique d'ordonnancement / paramètres dans attr |
Exemple — création simple
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *afficher(void *arg) {
char *msg = (char *) arg;
for (int i = 0; i < 5; i++) {
printf("[thread] %s — itération %d\n", msg, i);
sleep(1);
}
return NULL;
}
int main(void) {
pthread_t tid;
char *message = "Bonjour";
if (pthread_create(&tid, NULL, afficher, (void *) message) != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
/* Sans pthread_join, le main pourrait se terminer
avant le thread et l'éliminer. */
pthread_join(tid, NULL);
printf("[main] thread terminé.\n");
return 0;
}
/* compilation : gcc prog.c -o prog -lpthread */
ps aux dans un autre terminal, on ne voit qu'une seule entrée pour le processus (ex. exemple-pthread-create-1). Pour voir les threads, il faut utiliser ps maux.
6. Attributs de thread (pthread_attr_t)
Chaque thread est doté d'un certain nombre d'attributs, regroupés dans un type opaque pthread_attr_t. Ils sont fixés à la création via le 2ème argument de pthread_create.
- Si les attributs par défaut suffisent, on passe généralement un pointeur
NULL. - Sinon, on configure préalablement un objet
pthread_attr_t. - On commence par
pthread_attr_init(pthread_attr_t *attr)qui remplit la structure avec les valeurs par défaut. - Puis on utilise
pthread_attr_getXXX()etpthread_attr_setXXX()pour consulter / modifier l'attribut correspondant.
pthread_setname_np(pthread_self(), "worker-1");
7. Fin d'un thread
Lorsque la fonction principale d'un thread se termine, celui-ci est éliminé. La fonction doit renvoyer une valeur qui pourra être récupérée dans un autre thread. Autre possibilité — la primitive explicite :
void pthread_exit(void *retour);
/* Termine l'exécution du pthread et renvoie `retour`
(éventuellement NULL). */
main() exécute return ou exit(), tous les threads du processus sont terminés. Pour laisser les autres threads continuer après que le main a fini son rôle, utiliser pthread_exit dans le main (et pas return / exit).
Primitives connexes
int pthread_kill(pthread_t thread, int sig);
/* Envoyer un signal à un thread */
pthread_t pthread_self(void);
/* Retourne l'identificateur du thread appelant */
8. Attendre un thread — pthread_join()
int pthread_join(pthread_t thread, void **retval);
- Suspend le thread appelant jusqu'à la terminaison du thread désigné (annulé ou terminé via
pthread_exit). retval= pointeur sur une variable où le code de retour sera copié.- C'est l'équivalent de
waitpidpour les processus, sauf qu'on doit spécifier le TID du pthread à attendre. - Peut échouer si le TID n'existe pas ou s'il est détaché.
« Autant de threads que d'arguments. La fonction de chaque thread calcule le carré et le renvoie. Le thread principal récupère les résultats avec
pthread_join et les affiche. »
9. Détachement — pthread_detach()
int pthread_detach(pthread_t tid);
- Place un thread en cours d'exécution dans l'état détaché (sauf s'il a déjà été joint).
- Garantit que ses ressources mémoire seront immédiatement libérées à la fin de son exécution.
- Empêche les autres threads de se synchroniser sur la mort de ce thread via
pthread_join. - Si on ne veut pas avoir à gérer la fin d'un thread, on le « détache ».
- Échoue si le thread n'existe pas ou est déjà détaché.
pthread_detach, pile et code de retour sont libérés dès la fin du thread.
10. Synchronisation : les mutex
- Deux états : libre (pris par aucun thread) ou verrouillé (pris par un thread).
- Un thread qui verrouille un mutex le tient.
- Un seul thread peut tenir un mutex donné à la fois.
- En général : variables globales ou locales statiques.
Initialisation
pthread_mutex_t mutex =
PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(
pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);
mutex : pointeur vers la variable à initialiser.attr : pointeur vers un
pthread_mutexattr_t (ou NULL pour les valeurs par défaut).
Verrouillage / déverrouillage / libération
int pthread_mutex_lock(pthread_mutex_t *mutex);
/* Si mutex libre : verrouillé et attribué au thread appelant.
Sinon : blocage jusqu'à libération, puis verrouillé. */
int pthread_mutex_unlock(pthread_mutex_t *mutex);
/* Déverrouille le mutex. */
int pthread_mutex_destroy(pthread_mutex_t *mutex);
/* Détruit un mutex, libérant ses ressources.
Le mutex doit être déverrouillé. */
Exemple — compteur partagé protégé par un mutex
#include <stdio.h>
#include <pthread.h>
#define N_THREADS 4
#define N_ITER 100000
static long compteur = 0; /* variable PARTAGÉE */
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
void *incrementer(void *arg) {
for (int i = 0; i < N_ITER; i++) {
pthread_mutex_lock(&mtx); /* entrée section critique */
compteur++; /* accès protégé */
pthread_mutex_unlock(&mtx); /* sortie section critique */
}
return NULL;
}
int main(void) {
pthread_t th[N_THREADS];
for (int i = 0; i < N_THREADS; i++)
pthread_create(&th[i], NULL, incrementer, NULL);
for (int i = 0; i < N_THREADS; i++)
pthread_join(th[i], NULL);
printf("Compteur final = %ld (attendu %d)\n",
compteur, N_THREADS * N_ITER);
pthread_mutex_destroy(&mtx);
return 0;
}
/* Sans le mutex, le résultat final serait inférieur à
N_THREADS * N_ITER (race condition sur compteur++). */
pthread_mutex_lock / pthread_mutex_unlock en délimite les bornes.
11. Sémaphores (alternative)
Autre mécanisme de synchronisation, plus général que le mutex (compteur).
#include <semaphore.h>
int sem_init(sem_t *semaphore, int partage, int valeur);
int sem_wait(sem_t *sem_id); /* P : décrémente, bloque si 0 */
int sem_post(sem_t *sem_id); /* V : incrémente, réveille un attente */
Un mutex = sémaphore binaire avec notion de propriété. Un sémaphore compte et peut gérer N ressources simultanées (producteur-consommateur, pools).
★ Réviser le chapitre
❓ Q/R
Pourquoi utiliser des threads plutôt que fork ?
fork est lourd : père et fils sont indépendants (espaces d'adressage et piles séparés), changement de contexte coûteux, communication inter-processus lente, partage de données délicat. Les threads partagent l'espace d'adressage ⇒ communication rapide par mémoire, création légère, context-switch facilité.Que partagent les threads d'un même processus ? Que ne partagent-ils pas ?
Non partagé : identifiants de thread, pile, masque des signaux.
Signature exacte de pthread_create ?
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg);Renvoie 0 en cas de succès, sinon une valeur non nulle (
EAGAIN, EINVAL, EPERM).Quelle différence entre pthread_join et pthread_detach ?
pthread_join attend la fin d'un thread et récupère sa valeur de retour. pthread_detach dit au système que personne n'attendra ce thread : ses ressources sont libérées dès sa fin. Un thread détaché ne peut plus être joint.Que se passe-t-il si le main fait return alors que d'autres threads tournent ?
pthread_exit(NULL) dans le main à la place de return / exit.Qu'est-ce qu'un mutex ? Combien d'états ?
Deux manières d'initialiser un mutex ?
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;Dynamique :
pthread_mutex_init(&m, NULL); (ou avec attributs).Libération :
pthread_mutex_destroy(&m) (le mutex doit être déverrouillé).Quel piège mène à un interblocage trivial avec un mutex ?
lock attend pour toujours).Comment compiler un programme utilisant pthread ?
gcc prog.c -o prog -lpthread et inclure <pthread.h>.Comment voit-on les threads d'un processus avec ps ?
ps aux on ne voit que le processus (les threads partagent le PID). Pour afficher les threads il faut utiliser ps maux.🃏 Flashcards
Clique pour retourner.
Thread vs processus
1 différence clé.
tourne →fork sont indépendants.Partagé entre threads
Liste rapide.
tourne →NON partagé
3 choses.
tourne →pthread_create
Signature.
tourne →pthread_t *tid,
const pthread_attr_t *attr,
void *(*func)(void *),
void *arg);
Retour de pthread_create
Codes ?
tourne →EAGAIN (ressources), EINVAL (attr), EPERM (permissions).pthread_exit
Prototype.
tourne →pthread_join
Rôle + signature.
tourne →void **retval);
Équivalent de waitpid pour les threads.
pthread_self
Que renvoie-t-il ?
tourne →pthread_t) du thread appelant.pthread_detach
Pourquoi ?
tourne →pthread_join. Évite la saturation.Mutex
Définition + nb d'états.
tourne →Mutex statique
Initialisation.
tourne →PTHREAD_MUTEX_INITIALIZER;
Mutex dynamique
4 fonctions.
tourne →pthread_mutex_lock
pthread_mutex_unlock
pthread_mutex_destroy
Main qui se termine
Comportement.
tourne →main fait return / exit ⇒ tous les threads se terminent. Pour laisser tourner : pthread_exit dans le main.Compilation
Option gcc ?
✎ Quiz éclair
pthread_create en cas de succès ?*tid.main exécute return 0; alors que deux threads sont encore vivants. Que se passe-t-il ?pthread_exit dans le main.waitpid pour les threads ?pthread_join(tid, &retour) bloque jusqu'à la fin du thread désigné.pthread_detach ?detach, pile et code de retour sont conservés jusqu'à un pthread_join.pthread_mutex_lock appelé deux fois de suite dans le même thread sans unlock ?📌 À retenir
- Thread = processus léger ; partage l'espace d'adressage avec ses voisins.
- Partagé : globales, statiques, fd, PID, PPID, signaux (handlers), cwd, masque fichiers.
- Non partagé : TID, pile, masque des signaux.
- Avantages vs fork : mémoire partagée, création légère, context-switch rapide.
- Inconvénient : la synchronisation est à la charge du programmeur.
- Inclure
<pthread.h>· compiler avec-lpthread. pthread_create(&tid, attr, func, arg)renvoie 0 si succès.attr = NULL⇒ thread joignable, ordonnancement normal.pthread_exit(retour)termine le thread (à utiliser dansmainpour laisser tourner les autres).pthread_join(tid, &ret)attend la fin et récupère le retour (échoue si détaché).pthread_detach(tid)libère les ressources dès la fin du thread.pthread_self()= TID appelant ·pthread_kill(tid, sig)envoie un signal.- Mutex :
PTHREAD_MUTEX_INITIALIZER(statique) oupthread_mutex_init(dynamique). - Lock / unlock / destroy — jamais deux locks de suite sans unlock (interblocage).
- Sémaphore :
<semaphore.h>·sem_init / sem_wait / sem_post. - Observer les threads d'un processus :
ps maux(et nonps aux).