Français (French) Translation:

C++ Multithreading

Guide de Multithreading en C++ pour Débutants

Bonjour à tous, futurs superstars de la programmation ! Je suis ravi d'être votre guide dans ce voyage passionnant dans le monde du multithreading en C++. En tant que personne qui enseigne la programmation depuis des années, je peux vous assurer que bien que ce sujet puisse sembler intimidant au début, il est en réalité tout à fait fascinant une fois que vous avez pris la main. Alors, mettons-nous au travail !

Qu'est-ce que le Multithreading ?

Avant de plonger dans les détails, commençons par les bases. Imaginez-vous dans une cuisine, essayant de préparer un plat complexe. Vous pourriez faire tout cela étape par étape – couper les légumes, puis cuire les pâtes, puis préparer la sauce. Mais ne serait-il pas plus efficace si vous pouviez faire toutes ces tâches simultanément ? C'est essentiellement ce que le multithreading fait pour nos programmes !

Le multithreading permet à un programme d'effectuer plusieurs tâches simultanément. Chaque'une de ces tâches est appelée un "fil" (thread). C'est comme avoir plusieurs cuisiniers dans la cuisine, chacun responsable d'une partie différente du repas.

Maintenant, explorons comment nous pouvons utiliser cette puissance en C++ !

Création de Threads

Créer un thread en C++ est comme embaucher un nouveau cuisinier pour notre cuisine. Nous devons dire à ce cuisinier (thread) quelle tâche il doit effectuer. En C++, nous le faisons en utilisant la classe std::thread de la bibliothèque <thread>.

Regardons un exemple simple :

#include <iostream>
#include <thread>

void cuirePates() {
    std::cout << "Cuisson des pâtes..." << std::endl;
}

int main() {
    std::thread chefUn(cuirePates);
    chefUn.join();
    return 0;
}

Dans cet exemple :

  1. Nous incluons les bibliothèques nécessaires : <iostream> pour l'entrée/sortie et <thread> pour le multithreading.
  2. Nous définissons une fonction cuirePates() que notre thread exécutera.
  3. Dans main(), nous créons un thread appelé chefUn et lui disons d'exécuter la fonction cuirePates().
  4. Nous utilisons join() pour attendre que le thread termine sa tâche avant que le programme se termine.

Lorsque vous exécutez ce programme, vous verrez "Cuisson des pâtes..." affiché dans la console. Félicitations ! Vous avez juste créé votre premier thread !

Terminaison des Threads

Maintenant, que faire si notre cuisinier met trop de temps à cuire les pâtes ? Dans le monde de la programmation, nous pourrions avoir besoin de terminer un thread avant qu'il n'ait terminé sa tâche. Cependant, il est important de noter que terminer brutalement les threads peut entraîner des fuites de ressources et d'autres problèmes. Il est généralement préférable de concevoir vos threads pour qu'ils terminent naturellement ou répondent à des signaux de terminaison.

Voici un exemple de la façon dont nous pourrions configurer un thread pour répondre à un signal de terminaison :

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<bool> arreterThread(false);

void cuirePates() {
    while (!arreterThread) {
        std::cout << "Toujours en train de cuire des pâtes..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std::cout << "Cuisson des pâtes arrêtée !" << std::endl;
}

int main() {
    std::thread chefUn(cuirePates);

    std::this_thread::sleep_for(std::chrono::seconds(5));
    arreterThread = true;

    chefUn.join();
    return 0;
}

Dans cet exemple :

  1. Nous utilisons une variable atomic<bool> arreterThread pour communiquer en toute sécurité entre les threads.
  2. Notre fonction cuirePates() vérifie maintenant cette variable dans une boucle.
  3. Dans main(), nous laissons le thread s'exécuter pendant 5 secondes, puis nous définissons arreterThread sur vrai.
  4. Le thread répond en terminant sa boucle et en se terminant naturellement.

Passage d'Arguments aux Threads

Que faire si nous voulons donner plus d'instructions spécifiques à notre cuisinier ? En C++, nous pouvons passer des arguments à nos threads tout comme nous passons des arguments aux fonctions. Regardons comment :

#include <iostream>
#include <thread>
#include <string>

void cuirePlat(std::string plat, int temps) {
    std::cout << "Cuisson du " << plat << " pendant " << temps << " minutes." << std::endl;
}

int main() {
    std::thread chefUn(cuirePlat, "Spaghetti", 10);
    std::thread chefDeux(cuirePlat, "Pizza", 15);

    chefUn.join();
    chefDeux.join();

    return 0;
}

Dans cet exemple :

  1. Notre fonction cuirePlat() prend maintenant deux paramètres : le nom du plat et le temps de cuisson.
  2. Nous créons deux threads, chacun cuisant un plat différent pendant un temps différent.
  3. Nous passons ces arguments directement lors de la création des threads.

Cela montre la flexibilité des threads - nous pouvons avoir plusieurs threads effectuant des tâches similaires avec des paramètres différents !

Joindre et Détacher les Threads

Enfin, parlons de deux concepts importants : joindre et détacher les threads.

Joindre les Threads

Nous avons déjà vu join() dans nos exemples précédents. Lorsque nous appelons join() sur un thread, nous disons à notre programme principal d'attendre que ce thread termine avant de continuer. C'est comme attendre qu'un cuisinier prépare un plat avant de servir le repas.

Détacher les Threads

Parfois, nous pourrions vouloir laisser un thread s'exécuter indépendamment sans attendre qu'il se termine. C'est là que detach() entre en jeu. Un thread détaché continue d'exécuter en arrière-plan, même après la fin du programme principal.

Voici un exemple illustrant les deux :

#include <iostream>
#include <thread>
#include <chrono>

void cuireLentement(std::string plat) {
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << plat << " est prêt !" << std::endl;
}

void cuireRapidement(std::string plat) {
    std::cout << plat << " est prêt !" << std::endl;
}

int main() {
    std::thread cuisinierLent(cuireLentement, "Ragoût");
    std::thread cuisinierRapide(cuireRapidement, "Salade");

    cuisinierLent.detach();  // Laisser le cuisinier lent travailler en arrière-plan
    cuisinierRapide.join();   // Attendre que le cuisinier rapide termine

    std::cout << "Fin du programme principal. Le cuisinier lent pourrait encore travailler !" << std::endl;
    return 0;
}

Dans cet exemple :

  1. Nous avons deux cuisiniers : un qui cuisine lentement un ragoût et un qui prépare rapidement une salade.
  2. Nous détachons le thread du cuisinier lent, ce qui lui permet de continuer à travailler en arrière-plan.
  3. Nous joignons le thread du cuisinier rapide, en attendant que la salade soit prête.
  4. Le programme principal se termine, potentiellement avant que le ragoût soit prêt.
Méthode Description Cas d'utilisation
join() Attend que le thread se termine Lorsque vous avez besoin du résultat du thread avant de continuer
detach() Permet au thread de s'exécuter indépendamment Pour les tâches d'arrière-plan qui peuvent s'exécuter de manière autonome

Et voilà, mes amis ! Vous avez juste pris vos premiers pas dans le monde du multithreading en C++. Rappelez-vous, comme apprendre à cuisiner, maîtriser le multithreading prend de la pratique. N'ayez pas peur d'expérimenter avec ces concepts, et bientôt vous serez en train de préparer des programmes complexes et efficaces comme un chef dans la cuisine du code !

Bon codage, et que vos threads fonctionnent toujours en douceur !

Credits: Image by storyset