CodeGym /Cours /C# SELF /Introduction au multithreading

Introduction au multithreading

C# SELF
Niveau 55 , Leçon 0
Disponible

1. Introduction

Le multithreading, c'est comme le travail parallèle de plusieurs collègues au bureau : l'un imprime des documents, l'autre appelle un client, le troisième prépare le café (et, bien sûr, ce sont tous des développeurs). Si on n'avait qu'une seule personne, elle ferait tout à la suite, et le bureau s'ennuierait et la file pour le café serait interminable. En programmation la situation est analogue : un programme single-threaded ne peut exécuter qu'une tâche à la fois.

Imaginez que notre application effectue une opération longue, par exemple télécharger un fichier depuis Internet ou calculer un énorme tableau. Tout le reste est alors "gelé" — les boutons ne répondent pas, l'animation ne bouge pas, la phrase "ne répond pas" apparaît dans une fenêtre.

Le multithreading permet à l'application de faire plusieurs choses en même temps : l'interface reste réactive, les opérations s'exécutent en parallèle, et on n'a pas de scène du genre "Ordinateur, encore planté ?!".

Concepts de base et terminologie

Avant de plonger, clarifions ce qu'est un thread et en quoi il diffère d'un processus.

  • Process (Process) : Programme autonome avec son propre espace d'adressage, ses variables, ses ressources. Par exemple, chaque application lancée sous Windows est un processus séparé.
  • Thread (Thread) : Unité d'exécution au sein d'un processus. Un processus peut contenir un ou plusieurs threads qui partagent les mêmes ressources (mémoire, variables).

Pourquoi le multithreading suscite-t-il tant de questions ?

Parce que les threads sont des éléments imprévisibles : ils peuvent commencer à s'exécuter à tout moment, mélanger les données, s'interrompre mutuellement et mettre le bazar dans la mémoire si on ne gère pas l'ordre. Si ça ressemble à une maternelle sans surveillant — c'est exactement ça ! La discipline et le soin apportés aux threads sont la base pour écrire des programmes multithread fiables.

2. Histoire et rôle du multithreading en C# et .NET

Autrefois C# était surtout single-threaded, et les programmes étaient simples. Avec l'augmentation des besoins en performance, l'arrivée des processeurs multi-core et la nécessité de créer des applis réactives sans blocage de l'UI, .NET a introduit des outils pour le multithreading. Au départ il y avait le classique System.Threading.Thread, puis sont arrivés les tasks (Task), les méthodes asynchrones (async/await), le traitement parallèle de données (PLINQ) et des primitives de synchronisation de haut niveau.

C# est devenu une plateforme puissante où le multithreading est courant, pas une bizarrerie.

Visuel : processus et threads

Voici un schéma simple :


+--------------------------------------------------+
|                 Processus (votre programme)      |
|      +-------------+   +-------------+           |
|      |    Thread 1 |   |   Thread 2  |           |
|      +-------------+   +-------------+           |
|                ...                                 |
|      +-------------+                              |
|      |    Thread N |                              |
|      +-------------+                              |
+--------------------------------------------------+

Tous les threads dans le processus voient les mêmes variables et ressources partagées.

3. Comment créer un thread en C# ?

Commençons par le plus basique : la classe Thread du namespace System.Threading.

Exemple : on lance un second thread

Supposons que nous ayons une tâche longue — par exemple, calculer la somme des nombres de 1 à 10_000_000. Pendant le calcul, le thread principal affiche un message à l'utilisateur.


using System;
using System.Threading;

class Program
{
    // Méthode pour la deuxième tâche
    static void CalculateSum()
    {
        long sum = 0;
        for (int i = 1; i <= 10_000_000; i++)
            sum += i;
        Console.WriteLine($"[Thread 2] Somme: {sum}");
    }

    static void Main()
    {
        // On crée le thread en passant le délégué vers la méthode
        Thread thread = new Thread(CalculateSum);
        
        thread.Start(); // On lance le second thread

        // Le thread principal continue de fonctionner
        Console.WriteLine("[Thread 1] Salut ! On travaille en parallèle...");

        // On attend la fin du second thread avant de sortir
        thread.Join();

        Console.WriteLine("[Thread 1] Tout est terminé !");
    }
}

Que va-t-il se passer ?
À l'écran apparaîtra la ligne "[Thread 1] Salut ! On travaille en parallèle...", puis, quand le thread de calcul aura fini, il affichera la somme.

Problème typique : Qui est premier s'affiche en premier

Essayez d'exécuter ce code plusieurs fois — l'ordre d'affichage peut varier ! Parfois la somme apparaît en premier, parfois le message de salut. C'est ça le vrai multithreading — les programmes deviennent moins prédictibles, comme l'humeur d'un chat le lundi.

4. Zone mémoire : que voient les threads ?

Tous les threads d'un même processus ont accès aux mêmes variables (à moins qu'elles ne soient locales à une méthode). Si on modifie une variable dans un thread, les autres la verront !

Exemple : variable partagée


using System;
using System.Threading;

class Program
{
    static int counter = 0;

    static void Increment()
    {
        for (int i = 0; i < 1000; i++)
            counter++;
    }

    static void Main()
    {
        Thread t1 = new Thread(Increment);
        Thread t2 = new Thread(Increment);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine($"counter = {counter}");
    }
}

Combien s'attend-on à voir dans counter ? La logique dit 2000, puisque chaque thread incrémente 1000 fois.

Mais non ! Exécutez plusieurs fois — vous verrez des valeurs différentes : 1782, 1935, 1999…
Pourquoi ? C'est le problème classique de la Race Condition — les threads se "sautent" dessus entre la lecture -> l'incrément -> l'écriture, et certains incréments se perdent.

5. Nuances utiles

Comment les threads interagissent-ils avec l'interface ?

Dans les applications desktop modernes (WinForms/WPF/MAUI) le thread principal gère l'interface utilisateur. Toutes les actions utilisateur (clics, saisie) sont traitées dans ce thread. Les tâches en arrière-plan doivent s'exécuter dans d'autres threads, mais, par règle, on ne doit pas "toucher" l'UI directement depuis un autre thread. Ceci évite le chaos.

Dans la console il n'y a pas cette restriction, on peut appeler Console.WriteLine depuis n'importe quel thread. Cependant, dans de vraies applis, sans synchronisation correcte, l'interface peut "dérailler".

Séquencement et parallélisme

Tableau — pour fixer les différences.

Code single-thread Code multithread
Exécute les tâches les unes après les autres Les tâches peuvent s'exécuter simultanément
UI "gelée" pendant les tâches longues L'UI reste réactive
Lecture et écriture de variables simples Nécessite un contrôle d'accès aux données
Facile à déboguer Peut être difficile à déboguer

Points importants quand on travaille avec des threads

  • Variables partagées = risque partagé. Comme montré plus haut, si plusieurs threads utilisent une variable partagée — sans synchronisation des erreurs peuvent survenir ! (Détails dans les leçons suivantes.)
  • On peut "attendre" un thread avec Join(). La méthode Join() permet de suspendre le thread principal jusqu'à la fin du thread en arrière-plan. Utilisez-la si vous devez attendre un résultat.
  • Un thread ne peut pas être démarré deux fois. Après l'exécution d'un thread on ne peut pas le relancer — il faut créer un nouvel objet Thread.
  • Fin de thread. Un thread se termine quand la méthode qu'il exécute se termine. Terminer un thread de force est une mauvaise idée (la méthode Abort() est considérée comme nuisible et obsolète).

À quoi sert le multithreading en pratique ?

  • Applications UI : éviter de geler l'interface quand un téléchargement ou un calcul s'exécute en arrière-plan.
  • Serveurs et services : traiter simultanément les requêtes de nombreux clients.
  • Calculs haute performance : diviser une grosse tâche (par ex. traiter des millions d'enregistrements) en parties et les exécuter en parallèle.
  • Jeux, simulations, traitement de données : modéliser des systèmes complexes sans perdre en performance.

Problèmes du multithreading

Le multithreading donne de la puissance, mais ajoute de la complexité :

  • Race Condition (état de course) : quand plusieurs threads modifient simultanément les mêmes données, le résultat dépend de l'ordre des instructions, qui est imprévisible.
  • Deadlock (blocage mutuel) : les threads attendent les uns les autres et personne ne peut continuer.
  • Starvation (famine) : un thread est constamment privé d'accès à une ressource.

Dans cette leçon nous n'avons fait qu'évoquer les problèmes principaux du multithreading ; dans les suivantes nous apprendrons à les reconnaître et à les éviter. Pour l'instant — retenez juste que si tout "plante", ce ne sont pas forcément des bugs classiques, parfois ce sont des threads qui ont "organisé une fête".

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION