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.
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.
Par où commencer
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.
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.
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égion | Ce qu'elle contient | Comment elle évolue |
|---|---|---|
| Le code | Les instructions machine du programme | Fixe, et en lecture seule |
| Les données | Les variables globales et constantes | Taille connue au démarrage |
| Le tas | Tout ce que le programme alloue dynamiquement | Grandit à la demande |
| La pile | Les variables locales et la chaîne des appels | Monte 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 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.
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.
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.
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.
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.
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.
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.
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.
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
| Option | Ce qu'elle partage avec le parent |
|---|---|
CLONE_VM | L'espace d'adressage — même mémoire, donc mêmes variables |
CLONE_FILES | La table des fichiers ouverts |
CLONE_FS | Le répertoire courant et le masque de permissions |
CLONE_SIGHAND | Les gestionnaires de signaux |
CLONE_THREAD | Le 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.
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.
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.
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 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.
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.
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
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.
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.
| Situation | Ce que fait le système | Le nom |
|---|---|---|
| Plus de tâches prêtes que de sièges | Il les fait tourner à tour de rôle, très vite | Concurrence |
| Autant de sièges que de tâches, ou davantage | Elles avancent réellement en même temps | Parallélisme |
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.
| État | Ce que ça veut dire | Occupe-t-elle un siège ? |
|---|---|---|
| En cours | Elle exécute des instructions maintenant | Oui |
| Prête | Elle pourrait travailler, mais tous les sièges sont pris | Non, elle attend son tour |
| Bloquée | Elle attend une donnée du disque, du réseau, d'une base | Non — 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.
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
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.
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.
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.
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
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.
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 travail | Ce qui limite | Ordre de grandeur |
|---|---|---|
| Calcul intensif | Le processeur | Autour du nombre de cœurs |
| Attente d'entrées-sorties | La mémoire | Bien 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.
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.
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.
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
| Processus | Un périmètre : mémoire isolée, ressources, identité. Il ne calcule rien. |
| Thread | Un contexte d'exécution : program counter, registres, pile. C'est lui qui calcule. |
| Espace d'adressage | La vue de la mémoire propre à un processus. La source de l'isolation. |
| Copy-on-write | Ne rien recopier à la duplication, et ne copier une page qu'au premier écrit. |
| Cœur | L'unité qui exécute réellement. Une instruction à la fois. |
| Siège | Une place d'où soumettre du travail au cœur. Il y en a plus que de cœurs. |
| Concurrence | Plus de tâches que de sièges : elles progressent à tour de rôle. |
| Parallélisme | Assez de sièges : elles avancent au même instant. |
| Préemption | Le système reprend le siège sans demander, à la fin du quantum. |
| Changement de contexte | Sauvegarder 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 processus | Plusieurs threads | |
|---|---|---|
| Mémoire | Isolée, garantie par le matériel | Partagée, à surveiller |
| Coût de création | modéré, grâce à la copy-on-write | faible |
| Empreinte | un contexte complet chacun | une pile chacun, le reste en commun |
| Changement de contexte | bascule d'espace d'adressage | registres seulement |
| Rayon d'un plantage | le processus fautif | tout le processus, tous les threads |
| Échanger des données | canal explicite à mettre en place | immédiat, mais à protéger |
Les réflexes à garder
- Ce qui occupe un siège est toujours un thread, jamais un processus. Quand on dit « ce processus tourne sur le cœur 3 », on parle en fait de son unique thread.
- Le noyau ne distingue pas les deux : il ordonnance des tâches, et seul le degré de partage change.
- Une tâche bloquée ne coûte pas de processeur. C'est ce qui autorise à lancer bien plus de workers que de cœurs.
- Mesure le régime avant de dimensionner. Calcul ou attente : la règle s'inverse selon la réponse.
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.