← Tous les cours / Processus & Threads Cours
00 — VUE D'ENSEMBLE

Deux choses que le système sait faire tourner

Processus et thread. Deux mots qu'on emploie l'un pour l'autre, alors qu'ils ne désignent pas du tout la même chose.

Prends un fichier de code sur ton disque. Il ne fait rien : c'est un texte, une suite d'octets. Lance-le, et le système fabrique deux choses distinctes qu'on a pris l'habitude de confondre.

La première est un processus : un espace mémoire qui n'appartient qu'à lui, des fichiers ouverts, une identité auprès du système. C'est un périmètre. Il ne calcule rien.

La seconde est un thread : un program counter qui pointe la prochaine instruction, des registres, une pile. C'est un contexte d'exécution. C'est lui, et lui seul, qui exécute des instructions.

La distinction à ne plus perdre
Le processus délimite : il dit quelle mémoire est accessible et quelles ressources sont disponibles.
Le thread travaille : il avance dans les instructions, à l'intérieur de ce périmètre.

Ça ressemble à un détail de vocabulaire, et c'est en réalité ce qui explique la suite : pourquoi ajouter un thread coûte bien moins cher qu'ajouter un processus, pourquoi un plantage tue parfois un seul worker et parfois tout le serveur, et pourquoi ton processeur, lui, ne fait aucune différence entre les deux.

Ce que ce cours démonte

On avance des primitives vers leurs conséquences. D'abord ce qu'est un processus, puis ce qu'est un thread — et là une surprise attend ceux qui croyaient à deux objets distincts, parce que sous Linux ils n'en font qu'un. Ensuite comment le système décide qui travaille et quand, ce que coûte le fait de changer d'avis, et enfin comment dimensionner tout ça sans se tromper d'ordre de grandeur.

🧭Le fil rouge : chaque fois que tu hésiteras entre isolation et partage, souviens-toi qu'il n'existe pas de bon choix universel — seulement un compromis dont il faut connaître les deux plateaux.

Par où commencer

01 — L'ESPACE D'ADRESSAGE

Le processus

Un programme qui tourne, et surtout : une bulle mémoire que personne d'autre ne peut percer.

Quand tu lances un programme, le système ne se contente pas de lire ton fichier. Il fabrique autour de lui un environnement complet, et cet environnement porte un nom : le processus. Il reçoit un numéro d'identité, le PID, et surtout il reçoit un territoire.

L'espace d'adressage, ou le territoire privé

Ce territoire s'appelle l'espace d'adressage, et c'est la notion la plus importante de ce cours. Imagine que chaque processus se réveille en croyant disposer de toute la mémoire de la machine, numérotée depuis zéro. Il écrit à l'adresse 4096 sans se soucier de savoir si un voisin utilise déjà cette adresse — parce que son adresse 4096 et celle du voisin ne désignent pas le même endroit physique. Une pièce de matériel, l'unité de gestion mémoire, traduit en permanence les adresses que le processus manipule vers les emplacements réels de la RAM, en consultant une table qui n'appartient qu'à lui.

L'espace d'adressage
La vue de la mémoire propre à un processus. Deux processus peuvent utiliser la même adresse sans se marcher dessus : chacun a sa table de traduction, donc ses adresses aboutissent ailleurs.

C'est de là que vient l'isolation, et il faut bien mesurer ce que ça implique. L'isolation entre processus n'est pas une politesse que les programmes s'accordent entre eux, ni une vérification que le système ferait à chaque écriture. Elle est structurelle : un processus ne peut pas atteindre la mémoire d'un autre parce qu'aucune de ses adresses n'y mène. Le matériel refuse la traduction, et le programme fautif reçoit une erreur de segmentation.

🔑À retenir : l'isolation entre processus est gratuite pour toi et infranchissable pour eux. Tu ne l'as pas demandée, tu ne peux pas l'oublier, et un bug ne peut pas la contourner.

Ce qu'il y a dans le territoire

Cet espace n'est pas un bloc uniforme. Il s'organise en régions dont chacune a un usage bien défini, et connaître leurs noms t'aidera plus tard à lire un message d'erreur.

RégionCe qu'elle contientComment elle évolue
Le codeLes instructions machine du programmeFixe, et en lecture seule
Les donnéesLes variables globales et constantesTaille connue au démarrage
Le tasTout ce que le programme alloue dynamiquementGrandit à la demande
La pileLes variables locales et la chaîne des appelsMonte et descend au rythme des appels

Retiens surtout la dernière ligne, parce que c'est le seul élément de cette liste qui se dédoublera quand on ajoutera un thread. Tout le reste restera unique.

Le processus ne partage rien par accident

Deux processus qui veulent échanger des données doivent le demander explicitement. Ils passent par un canal que le système leur fournit — un tube, une socket, un fichier, une zone de mémoire délibérément exposée. Rien ne circule tout seul, et c'est précisément ce qui rend ce modèle si sûr et si lourd à la fois.

💡Le prix de la sûreté : cette étanchéité oblige à recopier les données pour les transmettre, et à créer un contexte complet pour chaque nouveau processus. On échange de la légèreté contre de la tranquillité — un marché qu'on renégociera à la page suivante.
02 — CE QUI EXÉCUTE

Le thread

Trois éléments, pas un de plus. Tout le reste appartient au processus et ne se duplique pas.

Un thread — fil d'exécution, en français, mais personne ne dit ça — vit à l'intérieur d'un processus. On le décrit souvent comme un « processus léger », et cette expression fait plus de mal que de bien : elle laisse croire à une version réduite du même objet. Un thread n'est pas un petit processus. C'est autre chose, et c'est ce que cette page va poser précisément.

Ce qu'un thread possède en propre

Ouvre un thread, regarde ce qu'il y a dedans, et tu seras surpris de la maigreur de l'inventaire.

L'anatomie complète d'un thread
Un program counter (« compteur de programme ») — l'adresse de la prochaine instruction à exécuter.
Un jeu de registres — l'état du processeur à cet instant précis.
Une pile — quelques mégaoctets pour ses variables locales et sa chaîne d'appels.

C'est terminé. Le code, le tas, les fichiers ouverts, la table de traduction des adresses : tout cela appartient au processus et n'est pas recopié quand tu ajoutes un thread. Voilà l'explication mécanique du fameux « un thread coûte moins cher » : tu ne dupliques pas le périmètre, tu ajoutes un contexte d'exécution à l'intérieur.

📖L'image la plus fidèle : un livre que plusieurs personnes lisent en même temps. Le texte existe en un seul exemplaire, la bibliothèque aussi, mais chaque lecteur a son marque-page et son carnet de notes. Le marque-page, c'est le program counter. Le carnet, c'est la pile.

Cette image dit aussi pourquoi la pile, elle, doit se dédoubler. Deux lecteurs qui partageraient le même marque-page se perdraient au premier chapitre. De la même façon, deux threads qui partageraient la même pile écraseraient mutuellement leurs variables locales et leur historique d'appels — ce ne serait plus deux threads, mais un thread confus.

📏Vérifie chez toi : ulimit -s te donne la taille de pile par défaut, exprimée en kilooctets. Sur beaucoup de systèmes tu liras 8192, soit 8 Mo. Si cette limite est réglée sur « illimité », le système retombe sur une valeur propre à l'architecture — 2 Mo sur la plupart. C'est ce chiffre qui explique qu'on ne lance pas cent mille threads sur une machine ordinaire : l'espace d'adressage se remplit de piles avant que le processeur ne sature.

Ce qu'un thread garantit

À l'intérieur d'un thread, les instructions se suivent dans l'ordre, et la pile raconte fidèlement l'histoire de comment tu es arrivé là. Cette propriété a un nom, la séquentialité locale, et c'est elle qui te permet de continuer à raisonner comme sur une machine simple alors que la machine réelle ne l'est plus depuis longtemps.

Dis-le autrement, et la portée devient claire : un thread est une machine séquentielle imaginaire, posée sur un substrat qui ne l'est pas. Tant qu'il n'y en a qu'un, l'illusion est parfaite et ne te coûte rien. Dès qu'il y en a plusieurs dans le même processus, c'est à toi de la maintenir — et c'est un autre cours.

Regarde un thread avancer

Le simulateur ci-dessous exécute un programme minuscule dans un seul thread. Suis la ligne surlignée : c'est le program counter. Regarde les registres changer, et la case mémoire se mettre à jour quand une instruction STORE passe. Mets-le en pas à pas si la lecture t'échappe.

Trois instructions distinctes pour incrémenter : lire, modifier, écrire. Retiens cette décomposition — c'est elle qui rendra les choses intéressantes quand un second thread arrivera.

03 — L'ASYMÉTRIE

Un processus a toujours un thread

Ce n'est pas une contrainte du système. C'est une définition, et deux cas limites l'éclairent.

Peut-on avoir un processus sans thread ? La question paraît théorique, mais y répondre consolide tout ce qui précède. Un processus sans thread, ce serait de la mémoire réservée et des fichiers ouverts, sans rien qui exécute quoi que ce soit. Aucune instruction ne serait jamais lue.

Alors non, ça n'existe pas. Quand le système démarre un programme, il crée exactement un thread — le thread principal — et tous les autres, s'il y en a, naîtront de celui-là. Il n'y a pas de processus vide auquel on viendrait ajouter un premier thread ; le processus doit son existence à ce thread.

L'asymétrie
Un thread ne peut pas exister hors d'un processus : il lui faut un espace d'adressage dans lequel travailler.
Un processus n'est qu'un contenant, et il tient son existence de son premier thread.

Deux cas limites, qui confirment la règle

Deux situations semblent contredire tout ça. En les regardant de près, elles font l'inverse : elles montrent que dès qu'on sépare le périmètre de l'exécution, on n'obtient plus un processus mais un objet incomplet.

Le zombie : plus rien qui exécute

Un processus se termine. Son dernier thread s'arrête, son espace d'adressage est rendu au système. Et pourtant une entrée subsiste dans la table du noyau, jusqu'à ce que son parent vienne récupérer son code de retour. Cet état porte le nom de zombie, et il est plus modeste qu'il n'y paraît : ce qui reste, c'est un numéro, un code de sortie et quelques statistiques d'utilisation. Un certificat de décès, pas un processus.

⚠️Pourquoi ça compte en pratique : tant que le parent ne vient pas les récupérer, ces certificats occupent une place dans la table des processus. Si un programme fabrique des enfants sans jamais les récupérer, cette table finit par se remplir et la machine ne peut plus créer de processus du tout. Quand un parent meurt avant ses enfants, le système confie ces orphelins à un processus de récupération, qui fait le ménage à sa place.

Le thread noyau : pas d'espace à soi

Le miroir exact. Le noyau fait tourner ses propres threads pour ses tâches internes — tu les vois entre crochets dans la liste des processus. Ils ont bien une exécution, mais pas d'espace d'adressage qui leur soit propre : ils travaillent dans celui du noyau. Une exécution sans espace propre, là où le zombie était un espace sans exécution.

🔑Séparées, les deux moitiés ne donnent jamais un processus utilisable. Il faut le périmètre et l'exécution, et c'est bien pour ça qu'on parle d'un seul objet dans la vie courante.
04 — UNE SEULE STRUCTURE

Sous Linux, le même objet

Le noyau ne connaît pas deux catégories. Il connaît des tâches, et un jeu d'options qui décide de ce qu'elles partagent.

Tu viens de lire deux pages sur la différence entre processus et thread. Voici le moment où ça se retourne : pour le noyau Linux, ces deux choses sont la même structure de données. Il ne tient pas deux listes. Il tient une liste de tâches, qu'il traite toutes de la même manière.

La différence ne porte donc pas sur la nature de l'objet créé, mais sur ce qu'il partage avec celui qui l'a créé. Et ce partage se décide au moment de la création, par un jeu d'options passées à un unique appel système.

Une création, plusieurs degrés de partage
Créer une tâche revient toujours au même appel. Ce sont les options qui décident : partage-t-on l'espace d'adressage ? les fichiers ouverts ? le répertoire courant ? Selon les cases cochées, on obtient ce qu'on appelle un thread, ou ce qu'on appelle un processus.

Les options qui comptent

OptionCe qu'elle partage avec le parent
CLONE_VML'espace d'adressage — même mémoire, donc mêmes variables
CLONE_FILESLa table des fichiers ouverts
CLONE_FSLe répertoire courant et le masque de permissions
CLONE_SIGHANDLes gestionnaires de signaux
CLONE_THREADLe groupe de tâches : la nouvelle tâche devient un thread du même processus

Sans aucune de ces options, la mémoire du parent est recopiée et l'on obtient ce que tout le monde appelle un processus. L'appel classique qui crée un processus n'est d'ailleurs rien d'autre que cette même création, sans partage.

Le noyau refuse certaines combinaisons

Les cases ne se cochent pas librement, et c'est là que la mécanique devient élégante. Le noyau impose une chaîne : demander CLONE_THREAD oblige à demander aussi CLONE_SIGHAND, qui à son tour oblige à demander CLONE_VM. Toute autre combinaison est rejetée.

🔗La conséquence, et elle est forte : on ne peut pas être un thread sans partager la mémoire. Ce n'est pas une convention d'usage ni une recommandation de style — c'est verrouillé jusque dans l'appel système, qui renvoie une erreur si tu essaies.

Le groupe de tâches

Quand plusieurs threads appartiennent au même processus, le noyau les regroupe sous un identifiant commun, et cet identifiant est simplement le numéro du premier d'entre eux. C'est ce numéro que ton programme obtient quand il demande « quel est mon PID ». Autrement dit, ce que tu appelles l'identité d'un processus est en réalité l'identité de son thread d'origine.

Compose toi-même

Coche des options et regarde ce que tu obtiens. Certaines combinaisons donnent un thread, d'autres un processus, d'autres encore un hybride qu'on rencontre rarement — et deux d'entre elles sont refusées par le noyau. Essaie notamment de demander un thread sans partager la mémoire.

05 — NAÎTRE ET CHANGER DE PEAU

Créer un processus, puis devenir un autre

Deux appels, deux rôles bien séparés — et une astuce qui rend la duplication presque gratuite.

Sur les systèmes de la famille Unix, lancer un programme se fait en deux temps, et cette séparation surprend toujours au premier abord. On ne dit pas au système « lance-moi ce programme ». On se duplique d'abord, puis l'un des deux exemplaires change de peau.

Premier temps : la duplication

Le premier appel crée une copie du processus courant. La copie hérite de tout — la mémoire, les fichiers ouverts, la position dans le code. Les deux exemplaires repartent du même endroit, à une différence près qui va tout permettre : la valeur renvoyée par l'appel n'est pas la même des deux côtés. Le parent reçoit le numéro de son enfant, l'enfant reçoit zéro. C'est ce seul écart qui leur permet de savoir qui ils sont et de prendre des chemins différents.

🔀Le motif que tu retrouveras partout : un test sur la valeur de retour, une branche pour le parent, une branche pour l'enfant. Le même code, exécuté par deux processus qui se séparent immédiatement.

La copie qui n'en est pas une

Recopier tout l'espace d'adressage à chaque création serait ruineux — et la plupart du temps parfaitement inutile, puisque l'enfant va souvent remplacer son code dans l'instant. Le système triche donc, avec une élégance qui vaut d'être comprise.

La copy-on-write (« copie à l'écriture »)
À la création, rien n'est recopié. Les deux processus consultent les mêmes pages de mémoire, marquées en lecture seule. La copie d'une page n'a lieu qu'au moment où l'un des deux tente d'y écrire — et seulement pour celle-là.

Le résultat est le meilleur des deux mondes. Les deux processus se croient propriétaires d'une mémoire entière et indépendante, ce qui est vrai de leur point de vue ; mais physiquement ils se partagent tout ce qu'ils n'ont pas modifié. Une duplication coûte donc à peu près le prix des pages effectivement écrites, pas celui de la mémoire totale.

Second temps : changer de peau

Le second appel remplace le contenu du processus par un autre programme. Le code, les données, le tas, la pile : tout est jeté et reconstruit à partir du nouvel exécutable. Ce qui ne change pas, c'est l'identité — le numéro du processus reste le même après l'opération.

🎭La bonne image : ce n'est pas une naissance, c'est un changement de costume. Le processus qui appelle ne revient jamais de cet appel, puisque le code qui l'a appelé n'existe plus. S'il en revient, c'est que l'opération a échoué.

Observe la séparation

Le simulateur ci-dessous exécute une duplication. Les deux tâches lisent la même case au départ, puis l'enfant seul y écrit. Regarde le panneau mémoire : deux processus apparaissent, et la case modifiée chez l'enfant se signale — c'est la copy-on-write qui vient d'avoir lieu. Le parent, lui, continue de lire la valeur d'origine.

Le parent affiche 10, l'enfant 15. Aucun des deux ne se trompe : ils ne regardent plus la même mémoire.

06 — CŒURS ET SIÈGES

Cœurs, sièges et SMT

« 12 cœurs, 24 threads » — et là, le mot thread ne veut plus dire du tout la même chose.

Ouvre la fiche technique de n'importe quel processeur récent et tu liras une formule du genre « 12 cœurs, 24 threads ». Le piège est là, tout entier : ce mot thread n'a presque rien à voir avec le thread dont on parle depuis quatre pages. Ce sont deux étages différents de la machine, et les confondre conduit à des raisonnements faux sur le dimensionnement.

Le cœur, seul à travailler vraiment

Le cœur
L'unité d'exécution réelle. Il a sa chaîne de traitement, ses unités de calcul, son banc de registres et ses caches rapprochés. C'est lui, et lui seul, qui exécute des instructions.

Un cœur ne fait qu'une chose à la fois. Si tu as douze cœurs, tu as douze exécutions réellement simultanées, pas une de plus.

Le thread matériel, ou l'art de boucher les trous

Voici le fait qui motive tout le reste : un cœur passe une énorme partie de son temps à attendre. Quand une instruction réclame une donnée qui n'est pas dans les caches, il faut aller la chercher en mémoire principale, et cette course dure des centaines de cycles. Pendant ce temps, les unités de calcul du cœur ne font rien du tout. Elles sont là, disponibles, inutilisées.

L'idée du multithreading simultané est de profiter de ces trous. On duplique le strict nécessaire pour tenir deux contextes d'exécution en vol — de quoi mémoriser où en est chacun — tout en gardant en commun les unités de calcul et les caches. Quand le premier contexte se met à attendre, le second glisse ses instructions dans les unités laissées libres.

💡Ce que ça n'apporte pas : un doublement. Le gain est partiel et dépend entièrement de la nature du travail — il vient de trous à combler, donc il est d'autant plus faible que le code sature déjà les unités de calcul. Sur certaines charges très denses, activer cette fonction peut même faire perdre du temps.
La bonne image : le siège
Un thread matériel n'est pas un cœur. C'est un siège depuis lequel on peut soumettre du travail au cœur. Sur un processeur « 12 cœurs, 24 threads », il y a vingt-quatre sièges mais seulement douze machines pour travailler.

Ce que le système en fait

Le système d'exploitation, lui, ne s'embarrasse pas de cette subtilité au quotidien. Il voit un certain nombre de sièges à remplir et il y installe les tâches prêtes à travailler — sans faire de différence, rappelons-le, entre celles que tu appelles processus et celles que tu appelles threads. Et c'est là que deux mots qu'on emploie souvent l'un pour l'autre deviennent nettement distincts.

SituationCe que fait le systèmeLe nom
Plus de tâches prêtes que de siègesIl les fait tourner à tour de rôle, très viteConcurrence
Autant de sièges que de tâches, ou davantageElles avancent réellement en même tempsParallélisme
🔑La concurrence est une question de structure : gérer plusieurs choses. Le parallélisme est une question d'exécution : en faire plusieurs au même instant. On peut avoir la première sans le second, et c'est même le cas le plus courant.
07 — QUI TRAVAILLE, QUAND

L'ordonnanceur

Il distribue les sièges, reprend la main sans prévenir, et sait qu'une tâche qui attend ne coûte rien.

Une tâche prête n'est pas une tâche qui travaille. Entre les deux, il y a une décision, prise des milliers de fois par seconde par une partie du noyau dont dépend la fluidité de toute la machine.

Les trois états qui comptent

À chaque instant, une tâche se trouve dans l'un de ces états, et savoir lequel explique presque tous les comportements de charge que tu observeras.

ÉtatCe que ça veut direOccupe-t-elle un siège ?
En coursElle exécute des instructions maintenantOui
PrêteElle pourrait travailler, mais tous les sièges sont prisNon, elle attend son tour
BloquéeElle attend une donnée du disque, du réseau, d'une baseNon — et c'est capital

Arrête-toi sur la dernière ligne, parce qu'elle est la clé de tout le dimensionnement. Une tâche qui attend une réponse du réseau ne consomme pas de processeur. Elle a rendu son siège, et l'ordonnanceur l'a immédiatement donné à quelqu'un d'autre. C'est pour cette raison qu'une machine peut faire avancer des centaines de tâches avec une poignée de cœurs, à condition que ces tâches passent leur temps à attendre — ce qui est exactement le cas d'une application web.

La préemption, ou pourquoi personne ne squatte

Que se passe-t-il si une tâche ne s'arrête jamais d'elle-même, parce qu'elle calcule sans jamais rien demander ? Sans mécanisme de protection, elle garderait son siège indéfiniment et gèlerait tout le reste. Le système lui accorde donc un quantum — une petite tranche de temps — au bout duquel il l'interrompt d'autorité pour donner sa place à une autre.

La préemption
Le système reprend le siège à une tâche sans lui demander son avis, à l'expiration de sa tranche de temps. La tâche interrompue ne s'en aperçoit pas : elle reprendra plus tard exactement là où elle en était, puisque son program counter et ses registres ont été mis de côté.

Compare toi-même

Voici les mêmes trois tâches, deux fois. À gauche un seul siège, à droite deux. Deux des tâches calculent sans jamais s'arrêter ; la troisième attend régulièrement une entrée-sortie. Lance les deux et compare les compteurs de ticks — mais regarde surtout les états défiler, et note le moment où la tâche qui attend libère sa place.

Un seul siège

Deux sièges

📐Lis bien le résultat : le temps total passe d'environ 97 ticks à 49, soit à peu près la moitié. Ce n'est pas une loi générale, c'est la conséquence de ce jeu de tâches précis — deux tâches calculatoires identiques se disputaient un seul siège, leur donner un siège chacune les fait avancer côte à côte.

Le plus instructif est ailleurs, dans ce que la tâche d'attente ne coûte pas. Ses attentes totalisent douze ticks, et pourtant elles ne s'ajoutent pas au total : pendant qu'elle patiente, les autres travaillent. Même avec un seul siège, l'attente s'était déjà recouverte toute seule. C'est la raison pour laquelle ajouter des sièges n'accélère que la part réellement calculée — et pourquoi, sur une charge faite surtout d'attente, en ajouter n'accélérerait presque rien.

08 — LE COÛT DU CHANGEMENT

Ce que coûte un changement de contexte

Changer de thread dans le même processus, ou changer de processus : ce n'est pas la même facture.

Chaque fois que l'ordonnanceur retire un siège à une tâche pour le donner à une autre, il y a un travail à faire, et ce travail n'est pas gratuit. Comprendre en quoi il consiste, c'est comprendre la vraie différence entre threads et processus — celle qui reste quand on a fini de parler de vocabulaire.

Le socle commun

Dans tous les cas, il faut mettre de côté l'état du processeur pour la tâche qui s'arrête, et restaurer celui de la tâche qui reprend. Le program counter, les registres, le pointeur de pile : tout cela est sauvegardé puis rechargé. C'est un travail court, quelques dizaines d'écritures en mémoire.

Le supplément qui change tout

Si les deux tâches appartiennent au même processus, on s'arrête là. Elles partagent le même espace d'adressage, donc la table de traduction des adresses ne change pas d'un poil. Le matériel continue de fonctionner avec ses caches d'adresses remplis, et le passage est bon marché.

Si les deux tâches appartiennent à des processus différents, il faut en plus changer d'espace d'adressage. Le processeur doit basculer sur une autre table de traduction, et les mécanismes qui accéléraient les traductions précédentes ne valent plus pour les nouvelles adresses. Le travail supplémentaire est réel, et il ne s'arrête pas à l'instant du basculement : la tâche qui reprend redémarre avec des caches moins bien garnis, donc plus lente pendant un moment.

Les deux tarifs
Entre threads du même processus — on sauvegarde des registres, on en restaure d'autres, on saute. La traduction des adresses est inchangée.
Entre processus différents — tout ce qui précède, plus le basculement d'espace d'adressage, plus une reprise à froid.
⚖️C'est ici que se niche l'avantage des threads, et il faut le formuler correctement : ils ne rendent pas une tâche plus rapide. Ils rendent le passage d'une tâche à l'autre moins coûteux.

Observe la nature du changement

Deux montages avec la même charge de travail. À gauche, deux threads dans un seul processus. À droite, deux processus d'un seul thread chacun. Le débit sera comparable — c'est le compteur de changements qui t'intéresse, et surtout la ligne où il s'inscrit.

Deux threads, un seul processus

Deux processus, un thread chacun

🎯Sois honnête sur ce que tu viens de voir. Le simulateur compte les changements et distingue leur nature ; il ne mesure pas leur coût. Il n'y a ici ni cache d'adresses ni table de traduction — ce sont des boîtes dans une page web. Le coût, lui, se raisonne : c'est ce qu'explique le paragraphe ci-dessus, pas ce que la démo prouve.

Cette précaution n'est pas de la modestie mal placée. Un simulateur qui prétendrait mesurer des nanosecondes te mentirait, et une démonstration qui ment est plus nuisible qu'une absence de démonstration. Ce que la démo établit réellement — et c'est déjà beaucoup — c'est que le nombre de basculements est le même dans les deux montages, et que seule leur nature diffère.

09 — DIMENSIONNER

Combien en lancer, vraiment

Le réflexe « autant que de cœurs » est juste pour du calcul, et faux pour presque tout le reste.

Tu sais maintenant qu'une tâche bloquée rend son siège. Cette seule phrase suffit à démonter l'intuition la plus répandue sur le dimensionnement, celle qui consiste à calquer le nombre de workers sur le nombre de cœurs.

Deux régimes, deux règles opposées

Tout dépend de ce que tes tâches font de leur temps, et la différence est si nette qu'on peut la trancher en deux cas.

Une tâche qui calcule — compression, traitement d'image, chiffrement — garde son siège du début à la fin. En lancer plus que tu n'as de sièges ne produit rien de bon : elles se voleront le processeur à tour de rôle, et tu paieras des changements de contexte sans rien gagner en débit. Ici, le nombre de cœurs est bien la borne.

Une tâche qui attend — une requête web qui interroge une base, appelle un cache, sollicite une API — passe l'essentiel de sa vie bloquée. Pendant cette attente, elle ne coûte pas un cycle de processeur. Tu peux donc en faire tourner beaucoup plus que tu n'as de sièges, et c'est même la seule façon d'occuper correctement ta machine.

Nature du travailCe qui limiteOrdre de grandeur
Calcul intensifLe processeurAutour du nombre de cœurs
Attente d'entrées-sortiesLa mémoireBien au-delà du nombre de cœurs

Pourquoi la mémoire prend le relais

Si le processeur n'est plus la contrainte, quelque chose d'autre doit l'être — sinon on lancerait des workers à l'infini. Ce quelque chose, c'est la mémoire. Chaque worker en réserve, qu'il travaille ou qu'il attende, et cette réservation ne disparaît pas pendant l'attente. Le calcul de dimensionnement devient donc une division : la mémoire disponible, divisée par l'empreinte d'un worker.

📐La conséquence contre-intuitive : sur une machine à douze cœurs, faire tourner quarante workers pour une application web n'est pas de l'imprudence, c'est du bon sens. Ce qui te fera tomber, ce n'est pas le processeur saturé — c'est le moment où la mémoire manquera.

Et si tu te trompes de régime ?

Les deux erreurs sont symétriques et se diagnostiquent différemment. Trop peu de workers pour un travail d'attente, et tu verras des requêtes patienter dans une file alors que ton processeur est à moitié endormi — le symptôme classique, et le plus frustrant, parce que la machine a l'air disponible. Trop de workers pour un travail de calcul, et tu verras le processeur saturé avec un débit qui stagne ou régresse, mangé par les basculements.

🔬Le seul conseil qui vaille : mesure avant de choisir. Regarde où tes tâches passent leur temps — bloquées ou occupées — parce que c'est cette proportion, et elle seule, qui décide de la règle à appliquer.

En pratique, dans un serveur PHP

Le cours PHP — Concurrence & Runtimes applique exactement ce raisonnement à un pool de workers réel, avec les réglages qui vont avec et un simulateur de saturation. Tu y verras aussi l'autre bout du compromis : ce que change le fait de mettre en commun des threads plutôt que des processus.

Et si tu veux voir une façon radicalement différente de gérer l'attente — sans multiplier les workers du tout —, le cours Event loop & asynchrone décrit le modèle qui prend le problème par l'autre bout.

10 — TOUT RASSEMBLER

Synthèse

Ce qu'il faut avoir retenu, et le réflexe à garder devant chaque décision.

Tout ce cours découle d'une seule séparation, posée à la première page : le processus délimite, le thread travaille. Si tu ne devais garder qu'une chose, garde celle-là — le reste s'en déduit.

Une phrase par notion

ProcessusUn périmètre : mémoire isolée, ressources, identité. Il ne calcule rien.
ThreadUn contexte d'exécution : program counter, registres, pile. C'est lui qui calcule.
Espace d'adressageLa vue de la mémoire propre à un processus. La source de l'isolation.
Copy-on-writeNe rien recopier à la duplication, et ne copier une page qu'au premier écrit.
CœurL'unité qui exécute réellement. Une instruction à la fois.
SiègeUne place d'où soumettre du travail au cœur. Il y en a plus que de cœurs.
ConcurrencePlus de tâches que de sièges : elles progressent à tour de rôle.
ParallélismeAssez de sièges : elles avancent au même instant.
PréemptionLe système reprend le siège sans demander, à la fin du quantum.
Changement de contexteSauvegarder un état, restaurer un autre. Plus cher si l'espace d'adressage change.

Le compromis, en une ligne

Chaque fois que tu hésiteras, ramène la question à celle-ci : préfères-tu l'isolation ou le partage ? Les deux plateaux sont connus, et aucun ne gagne dans l'absolu.

Plusieurs processusPlusieurs threads
MémoireIsolée, garantie par le matérielPartagée, à surveiller
Coût de créationmodéré, grâce à la copy-on-writefaible
Empreinteun contexte complet chacunune pile chacun, le reste en commun
Changement de contextebascule d'espace d'adressageregistres seulement
Rayon d'un plantagele processus fautiftout le processus, tous les threads
Échanger des donnéescanal explicite à mettre en placeimmédiat, mais à protéger

Les réflexes à garder

Où aller ensuite

Ce cours s'est arrêté au bord du sujet le plus délicat. Dès que plusieurs threads partagent une mémoire, tu perds le droit d'affirmer qu'entre deux lignes de ton code personne n'a touché à tes données — et il faut alors des outils pour reconstruire cette certitude. C'est l'objet du cours Synchronisation & Interblocages.