Maîtriser le multithreading en C++ : Programmation concurrente efficace avec POSIX
Le multithreading est une forme spécialisée de multitâche. Le multitâche permet à un ordinateur d'exécuter plusieurs programmes simultanément. On distingue deux types :
- Multitâche basé sur les processus : exécution concurrente de programmes distincts.
- Multitâche basé sur les threads : exécution concurrente de parties d'un même programme.
Un programme multithreadé contient au moins deux parties s'exécutant simultanément. Chaque partie est un thread, définissant un chemin d'exécution distinct.
C++ ne possède pas de support natif pour le multithreading avant C++11 ; il s'appuie sur l'API du système d'exploitation. Ce tutoriel suppose un environnement Linux et utilise POSIX Threads (Pthreads), disponible sur les systèmes Unix-like (FreeBSD, NetBSD, GNU/Linux, macOS, Solaris).
Création de threads
La routine pthread_create crée un nouveau thread POSIX :
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
| Paramètre | Description |
|---|---|
thread | Identifiant unique du thread créé (sortie). |
attr | Attributs du thread (ou NULL pour les valeurs par défaut). |
start_routine | Fonction que le thread exécutera. |
arg | Argument unique passé à la fonction (pointeur void*). |
Le nombre maximal de threads est dépendant de l'implémentation. Une fois créés, les threads sont pairs et peuvent en créer d'autres ; il n'y a pas de hiérarchie implicite.
Terminaison des threads
La fonction pthread_exit termine explicitement un thread :
#include <pthread.h>
void pthread_exit(void *status);
Si main() se termine avant ses threads et appelle pthread_exit(), les autres threads continuent. Sinon, ils sont arrêtés automatiquement à la fin de main().
Exemple : 5 threads « Hello World »
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
void *PrintHello(void *threadid) {
long tid = (long)threadid;
cout << "Hello World! Thread ID, " << tid << endl;
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
int rc, i;
for (i = 0; i < NUM_THREADS; i++) {
cout << "main() : création du thread, " << i << endl;
rc = pthread_create(&threads[i], NULL, PrintHello, (void *)i);
if (rc) {
cout << "Erreur : impossible de créer le thread, " << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
Compilation : g++ test.cpp -lpthread
Sortie typique (l'ordre peut varier) :
main() : création du thread, 0
main() : création du thread, 1
main() : création du thread, 2
main() : création du thread, 3
main() : création du thread, 4
Hello World! Thread ID, 0
Hello World! Thread ID, 1
Hello World! Thread ID, 2
Hello World! Thread ID, 3
Hello World! Thread ID, 4
Passage d'arguments aux threads
On utilise souvent une structure pour passer plusieurs arguments :
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
struct thread_data {
int thread_id;
char *message;
};
void *PrintHello(void *threadarg) {
struct thread_data *my_data = (struct thread_data *)threadarg;
cout << "Thread ID : " << my_data->thread_id;
cout << " Message : " << my_data->message << endl;
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
struct thread_data td[NUM_THREADS];
int rc, i;
for (i = 0; i < NUM_THREADS; i++) {
cout << "main() : création du thread, " << i << endl;
td[i].thread_id = i;
td[i].message = "Ceci est un message";
rc = pthread_create(&threads[i], NULL, PrintHello, (void *)&td[i]);
if (rc) {
cout << "Erreur : impossible de créer le thread, " << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
Joindre et détacher les threads
Deux routines permettent de synchroniser les threads :
pthread_join(threadid, status)
pthread_detach(threadid)
pthread_join(): bloque le thread appelant jusqu'à la fin du thread spécifié. Seuls les threads créés comme joinable peuvent être joints.pthread_detach(): marque le thread comme détaché ; il ne peut plus être joint et ses ressources sont libérées automatiquement à sa fin.
Exemple : attendre la fin des threads avec pthread_join
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
using namespace std;
#define NUM_THREADS 5
void *wait(void *t) {
long tid = (long)t;
sleep(1);
cout << "Thread en sommeil " << endl;
cout << "Thread avec id : " << tid << " ...sortie " << endl;
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
pthread_attr_t attr;
void *status;
int rc, i;
// Initialiser les attributs et rendre les threads joinable
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for (i = 0; i < NUM_THREADS; i++) {
cout << "main() : création du thread, " << i << endl;
rc = pthread_create(&threads[i], &attr, wait, (void *)i);
if (rc) {
cout << "Erreur : impossible de créer le thread, " << rc << endl;
exit(-1);
}
}
// Libérer les attributs et attendre les threads
pthread_attr_destroy(&attr);
for (i = 0; i < NUM_THREADS; i++) {
rc = pthread_join(threads[i], &status);
if (rc) {
cout << "Erreur : impossible de joindre, " << rc << endl;
exit(-1);
}
cout << "Main: thread terminé id :" << i;
cout << " sortie avec statut :" << status << endl;
}
cout << "Main: fin du programme." << endl;
pthread_exit(NULL);
}
Sortie :
main() : création du thread, 0
main() : création du thread, 1
main() : création du thread, 2
main() : création du thread, 3
main() : création du thread, 4
Thread en sommeil
Thread avec id : 0 ....sortie
Thread avec id : 1 ....sortie
Thread avec id : 2 ....sortie
Thread avec id : 3 ....sortie
Thread avec id : 4 ....sortie
Main: thread terminé id :0 sortie avec statut :0
Main: thread terminé id :1 sortie avec statut :0
Main: thread terminé id :2 sortie avec statut :0
Main: thread terminé id :3 sortie avec statut :0
Main: thread terminé id :4 sortie avec statut :0
Main: fin du programme.
Ce tutoriel couvre les bases du multithreading POSIX en C++. Pour les projets modernes C++ (C++11 et ultérieur), privilégiez std::thread, std::mutex, std::condition_variable et les algorithmes parallèles de la bibliothèque standard.
Langue C
- Maîtriser les opérateurs C++ : Guide complet avec exemples pratiques
- Commentaires C++ : Bonnes pratiques pour un code lisible et maintenable
- Maîtriser les templates de classe en C++ : Guide pratique
- Langage C++ : Vue d'ensemble et caractéristiques clés
- Constantes et littéraux en C++ : Types, syntaxe et bonnes pratiques
- Guide complet des opérateurs C++ : Types, utilisation et bonnes pratiques
- Maîtriser les types numériques en C++ : Guide pratique
- Comprendre les références en C++ : Concepts clés et différences avec les pointeurs
- Maîtriser les templates C++ : Guide de la programmation générique