Geonode logo
Geonode Team

Geonode Team

Mis à jour : 7 octobre 2026

Publié : 2 septembre 2026

Concurrence ou parallélisme : quelle est la différence ?

La concurrence consiste à gérer plusieurs tâches à la fois. Le parallélisme consiste à effectuer plusieurs tâches à la fois. C'est la définition classique, mais à elle seule, elle n'explique rien. Cette distinction prend tout son sens dès lors que l'on se pose une question concrète : pourquoi l'ajout de threads rend-il un programme plus rapide et un autre plus lent ? Cet article répond à cette question, en détaillant les différences entre Python, Go et Node, et en expliquant comment déterminer lequel de ces langages correspond réellement à vos besoins.

Quelques précisions sur l'auteur de cet article et ses motivations. Nous sommes Geonode et nous vendons des proxys, ce qui nous place constamment au cœur de ce sujet : le web scraping est la charge de travail par excellence limitée par les E/S, et « mon scraper est lent » est l'une des remarques les plus fréquentes que l'on nous adresse. Voici donc la vérité avant d’entrer dans la théorie : si votre scraper est lent parce qu’il récupère une page à la fois, acheter des proxys ne l’accélérera pas. La concurrence est une propriété de votre code. Une centaine de points de terminaison proxy et une boucle séquentielle vous donneront un scraper séquentiel avec une centaine de points de terminaison inactifs. Commencez par régler le problème de la concurrence. Les proxys résolvent un autre problème : celui que vous rencontrez après que la concurrence fonctionne, lorsque la cible commence à limiter le débit de l’adresse qui envoie soudainement cinquante requêtes par seconde. Ces deux problèmes sont bien réels. Ce ne sont pas les mêmes problèmes et ils n’ont pas la même solution.

Cela étant dit, voici la distinction.

La distinction en une phrase

La formulation de Rob Pike, tirée de son intervention sur le sujet, reste la plus claire, et le blog Go l'énonce sans détour :

La concurrence est « la composition de processus s'exécutant indépendamment ».

Le parallélisme est « l’exécution simultanée de calculs (éventuellement liés) ».

Relisez ces définitions deux fois, car la différence réside dans l’accent mis sur tel ou tel aspect. La concurrence concerne la structure — la manière dont on décompose un problème en éléments pouvant s’exécuter indépendamment. Le parallélisme concerne l’exécution — le nombre de ces éléments qui s’exécutent physiquement au même instant.

La conséquence que l’on oublie souvent : la concurrence, c’est ce que l’on écrit ; le parallélisme, c’est ce que fait la machine. Vous pouvez écrire un programme concurrent et l’exécuter sur un seul cœur, où rien n’est jamais simultané, et il restera tout de même concurrent. Vous avez décrit des tâches indépendantes ; le moteur d’exécution les entrelace. Et un programme concurrent exécuté sur huit cœurs peut devenir parallèle, si le moteur d’exécution et la charge de travail le permettent.

C’est cette asymétrie qui explique pourquoi ces deux termes ne sont pas interchangeables. La concurrence permet le parallélisme sans le garantir. Le parallélisme sans structure concurrente n’est tout simplement pas possible.

Pourquoi la confusion persiste

Trois raisons, qu’il est utile de nommer.

Le comportement observable est souvent identique. Un programme concurrent sur un seul cœur et un programme parallèle sur quatre cœurs donnent tous deux l’impression que « plusieurs choses se passent en même temps ». Vu de l’extérieur, il est impossible de distinguer les deux. On ne s’en rend compte que lorsqu’on ajoute des cœurs et que l’on constate qu’il n’y a aucune amélioration.

Le vocabulaire de chaque langage manque de cohérence. Le module threading de Python offre la concurrence, mais historiquement pas le parallélisme. Le module multiprocessing de Python offre les deux. Les async / await de JavaScript offrent la concurrence, mais jamais le parallélisme pour votre propre code. Les goroutines de Go offrent à la fois la concurrence et le parallélisme, dans la mesure où GOMAXPROCS. Les mêmes termes utilisés dans la documentation ont des significations différentes selon les écosystèmes.

La plupart du temps, vous n’avez pas besoin de vous en soucier, jusqu’à ce que cela devienne soudainement nécessaire. Pour une charge de travail liée aux E/S, la distinction est presque purement théorique : la concurrence à elle seule suffit pour obtenir tous les avantages. Pour une charge de travail liée au CPU, c’est tout autre chose, car la concurrence à elle seule ne vous apporte rien. Le problème, c’est que les gens apprennent le modèle qui a fonctionné pour leur problème lié aux E/S et l’appliquent à un problème lié au CPU.

Comment chaque langage s'y prend concrètement

Environnement d'exécutionMécanisme de concurrenceParallélisme réel pour votre codeLimites
Python (version par défaut)threading, asyncioUniquement via multiprocessingLe GIL sérialise l’exécution du bytecode
Python (version multithread)threading, asyncioOui, les threads s’exécutent en parallèleSurcoût lié au mode monothread, maturité de l’écosystème
Gogoroutines + canauxOui, jusqu’à GOMAXPROCSL’état partagé nécessite toujours une synchronisation
Node.jsboucle d’événements, async/awaitUniquement via worker_threads ou des processus enfantsUn seul callback gourmand en ressources CPU bloque tout
Java / C#threads, pools de threadsOuiComplexité de l’état partagé et modifiable
Rustasync + threadsOuiLe compilateur vous oblige à écrire du code correct dès le départ

Node.js est l’illustration la plus claire de la concurrence sans parallélisme. La documentation officielle le formule avec précision : la boucle d’événements « permet à Node.js d’effectuer des opérations d’E/S non bloquantes — bien qu’un seul thread JavaScript soit utilisé par défaut — en déléguant les opérations au noyau du système dès que possible ». Le noyau est multithread ; votre code JavaScript ne l’est pas. La boucle parcourt six phases — temporisateurs, callbacks en attente, inactivité/préparation, interrogation, vérification, fermeture des callbacks — et lorsqu’une opération se termine, « le noyau en informe Node.js afin que le callback approprié puisse être ajouté à la file d’attente d’interrogation pour être finalement exécuté ».

La conséquence pratique en découle directement. Traiter dix mille requêtes HTTP simultanées dans Node.js est un jeu d’enfant, car l’attente se fait au niveau du noyau. Une seule fonction gourmande en ressources CPU s’exécutant pendant deux secondes bloque l’ensemble du processus, car il n’y a qu’un seul thread pour l’exécuter et rien ne peut la préempter.

Go adopte l’approche inverse : les goroutines sont suffisamment peu coûteuses à créer pour en générer des milliers, et le planificateur les répartit entre les threads du système d’exploitation jusqu’à atteindre l’GOMAXPROCS (nombre maximal de goroutines), qui correspond par défaut au nombre de cœurs disponibles. Go offre donc à la fois la concurrence et le parallélisme à partir d’une même construction. C’est la raison pour laquelle Pike a donné cette conférence : cette distinction revêt une importance capitale dans un langage où l’on dispose des deux et où l’on peut donc les confondre.

La question décisive : votre charge de travail est-elle limitée par les E/S ou par le processeur ?

Tout ce qui précède se résume à une seule question concernant votre charge de travail, et il vaut mieux la mesurer plutôt que de se contenter d’hypothèses.

« Limité par les E/S » signifie que votre programme passe la majeure partie de son temps à attendre : une réponse réseau, une lecture sur disque, une requête de base de données. Pendant cette attente, le processeur est inactif. La concurrence est la réponse correcte et suffisante, car elle vous permet de lancer la prochaine attente alors que la précédente est encore en cours. Le parallélisme n'apporte pratiquement rien : huit cœurs qui attendent une réponse du réseau ne sont pas plus rapides qu'un seul cœur dans la même situation.

« CPU-bound » signifie que votre programme passe la majeure partie de son temps à effectuer des calculs : analyse syntaxique, compression, hachage, transformation. Le processeur est saturé. La concurrence à elle seule ne change rien : l’entrelacement de deux calculs sur un même cœur prend autant de temps au total que de les exécuter en séquence, plus la surcharge liée à la commutation. Seul le parallélisme est utile, et uniquement dans la limite du nombre de cœurs physiques.

Pour déterminer de quel cas il s’agit, mesurez plutôt que de raisonner. Sous Linux, la commande time vous donne immédiatement la réponse : comparez le temps écoulé réel au temps CPU utilisateur plus système. Si le temps réel est bien supérieur au temps CPU, vous êtes en attente — le système est limité par les E/S. S’ils sont proches, vous effectuez des calculs — le système est limité par le CPU.

Le web scraping est un exemple utile car il combine les deux, de manière séquentielle. La récupération des pages est fortement limitée par les E/S ; l’analyse du code HTML qui suit est limitée par le CPU. Une architecture adéquate utilise la concurrence pour la récupération et le parallélisme pour l’analyse, tandis que l’erreur courante consiste à appliquer une seule et même stratégie aux deux étapes. Un scraper effectuant 200 récupérations simultanées pour alimenter un analyseur à thread unique n’est pas un scraper rapide : c’est un récupérateur rapide avec une file d’attente qui s’accumule derrière lui.

Le GIL de Python et ce qu’a changé le « Free Threading »

Python mérite une section à part entière, car la situation a véritablement évolué et une grande partie de ce que vous lirez à ce sujet est désormais obsolète.

Historique : le verrou global de l’interpréteur (GIL) de CPython n’autorisait qu’un seul thread à la fois à exécuter le bytecode Python. Les threads offraient donc de la concurrence, mais pas de parallélisme. Le GIL étant levé lors des opérations d’E/S, les E/S multithread fonctionnaient correctement ; ce n’était pas le cas pour le multithreading lié au processeur, et l’utilisation de multiprocessing constituait une solution de contournement.

Ce qui a changé : à partir de la version 3.13, CPython propose une version optionnelle avec le GIL désactivé. La documentation sur le « free-threading » le décrit clairement : « L’exécution en threads libres permet d’utiliser pleinement la puissance de traitement disponible en exécutant des threads en parallèle sur les cœurs de processeur disponibles. »

La PEP 779, acceptée par le Conseil de pilotage le 16 juin 2025 avec le statut « Final », a défini les critères permettant de faire passer le « free-threading » du statut expérimental à celui de fonctionnalité officiellement prise en charge, avec pour objectif la version Python 3.14 pour cette phase.

Quatre points pratiques à connaître avant de vous lancer :

Ce n’est pas la version par défaut. Vous devez l’obtenir ou la compiler vous-même — à partir du code source, ce qui implique l’option de configuration --disable-gil. Vérifiez la version que vous utilisez avec python -VV, qui affiche « free-threading build », ou sys._is_gil_enabled(), qui renvoie « False » lorsque le GIL est désactivé.

Le code mono-thread devient plus lent. La documentation indique que, dans la suite pyperformance, « la surcharge moyenne varie d’environ 1 % sur macOS aarch64 à 8 % sur les systèmes Linux x86-64 ». La PEP 779 indique que le Conseil de pilotage s’attend à ce que Python en mode « free-threading » « soit environ 10 à 15 % plus lent », avec 15 % comme objectif ferme pour la phase II, et accepte une augmentation de 20 % (moyenne géométrique) de l’utilisation de la mémoire comme « le coût d’un mode « free-threading » efficace et sûr ». Si votre programme est monothread, cette version constitue une régression pure et simple.

Vous pouvez réactiver le GIL lors de l’exécution. Les versions à threads libres prennent en charge l’exécution avec le GIL activé via la variable d’environnement PYTHON_GIL ou l’option -X gil — ce qui est utile lorsqu’une dépendance présente un comportement inapproprié.

Vos dépendances constituent la contrainte. Les extensions C doivent être compilées de manière à déclarer la prise en charge du « free-threading ». L’écosystème a considérablement évolué, mais « ça fonctionne sur ma machine avec du Python pur » n’est pas synonyme de « ma pile scientifique fonctionne ».

Pour les cas liés aux E/S, qui prédominent dans le scraping et l’utilisation des API, rien de tout cela ne change votre décision : asyncio ou un pool de threads sur la version standard vous offre déjà tout ce que la concurrence peut apporter. Le multithreading libre est important lorsque c’est l’étape d’analyse, et non celle de récupération, qui constitue votre goulot d’étranglement.

Exemple concret : récupération de 10 000 URL

Des chiffres concrets permettent de bien mettre en évidence la différence. Supposons que chaque requête prenne 200 ms et que l'analyse de chaque réponse nécessite 50 ms de temps CPU, sur une machine à quatre cœurs.

Séquentiel. 10 000 × 250 ms = 2 500 secondes, soit environ 42 minutes. Le processeur est inactif pendant 80 % de ce temps.

Récupération simultanée, analyse séquentielle. Avec 100 requêtes simultanées, la récupération tombe à environ 20 secondes en temps réel. L’analyse reste inchangée : 10 000 × 50 ms = 500 secondes. Total : environ 520 secondes, soit environ 9 minutes. Un gain de 4,8 fois — et remarquez où le temps a été gagné. La récupération représentait 80 % du temps d’exécution initial et ne représente désormais plus que 4 % du nouveau temps d’exécution. L’analyse, que vous n’avez pas modifiée, représente désormais 96 % du temps total.

Récupération simultanée, analyse parallèle sur quatre cœurs. L’analyse tombe à environ 125 secondes. Soit un total d’environ 145 secondes, soit environ 2,5 minutes. Un gain de 17 fois par rapport à l’exécution séquentielle.

Ces chiffres nous enseignent trois leçons.

Premièrement, le gain le plus important provient de la résolution du problème d’E/S grâce à la concurrence, et cela ne coûte presque rien : pas de cœurs supplémentaires, pas de problèmes d’état partagé, juste une boucle différente.

Deuxièmement, une fois que vous avez résolu le goulot d’étranglement dominant, le suivant prend immédiatement le relais. C’est la loi d’Amdahl dans sa forme la plus concrète : optimiser une étape qui représente 20 % du temps d’exécution ne peut pas vous faire gagner plus de 25 % de vitesse, même si vous l’éliminez complètement. Mesurez toujours avant d’optimiser, puis mesurez à nouveau après, car le résultat change.

Troisièmement — et c’est là que notre intérêt commercial entre en jeu, il faut donc en tenir compte — dès que vous passez d’une requête à la fois à une centaine, vous devenez visible. Une seule adresse effectuant 500 requêtes par seconde vers un hôte sera limitée en débit, puis bloquée. Ce n’est pas un problème de concurrence et aucune mesure d’asyncio, quelle qu’elle soit, ne peut y remédier ; c’est un problème de répartition, et c’est à cela que servent les proxys. Notre trafic résidentiel commence à 0,79 $/Go et celui des centres de données à 0,14 $/Go, selon notre page de tarification en septembre 2026. Mais attention à l’ordre des priorités : la concurrence d’abord, puis les proxys lorsque la concurrence crée un problème qu’elle ne peut pas résoudre. Procéder dans l’ordre inverse revient à payer pour une bande passante que vous n’avez aucun moyen d’utiliser.

Quand la concurrence cesse d’être bénéfique

Augmenter la concurrence entraîne des rendements décroissants, puis négatifs, et le point de basculement survient plus tôt que la plupart des gens ne le pensent.

Limites de connexion. Les systèmes d’exploitation plafonnent le nombre de descripteurs de fichiers ouverts. Les serveurs limitent le nombre de connexions simultanées par client. Dix mille requêtes simultanées provenant d’une seule machine atteindront l’un de ces plafonds bien avant d’atteindre la limite du processeur, et le mode de défaillance se traduit généralement par une erreur confuse plutôt que claire.

Mémoire. Chaque requête en cours de traitement contient des tampons, des en-têtes analysés et des données de réponse en attente. Dix mille requêtes simultanées occupant chacune 100 Ko représentent un gigaoctet de mémoire qui ne sert qu’à attendre.

Surcoût lié au changement de contexte. Les threads du système d’exploitation ne sont pas gratuits : chacun comporte une pile et un coût d’ordonnancement. C’est exactement la raison d’être des goroutines et des coroutines : elles sont suffisamment peu coûteuses pour qu’il soit raisonnable d’en utiliser des milliers, ce qui n’est pas le cas pour des milliers de threads du système d’exploitation.

La tolérance de la cible. L’autre extrémité de la connexion a ses propres contraintes. Au-delà d’un certain débit, une concurrence supplémentaire génère des codes d’erreur 429 et 503 plutôt que des données, et votre débit effectif diminue à mesure que vous en ajoutez. Il s’agit là du plafond le plus courant en conditions réelles et de celui qui est le moins souvent mesuré, car les requêtes « fonctionnent » toujours — elles renvoient simplement des erreurs qu’une boucle de réessai répète consciencieusement.

L’approche pratique est peu glamour : commencez par une limite de concurrence modeste, mesurez le nombre de requêtes abouties par seconde plutôt que le nombre de requêtes tentées, puis augmentez progressivement jusqu’à ce que le débit cesse de s’améliorer. Il atteindra un plateau, puis commencera à baisser. Le point optimal se situe au niveau de ce plateau, et il s’agit généralement d’un nombre bien plus faible que ce que l’intuition suggère — souvent des dizaines plutôt que des centaines.

Quand ni l'un ni l'autre n'est nécessaire

Cela vaut la peine d'être précisé, car « rendre le code concurrent » est devenu un réflexe.

Lorsque la tâche est vraiment minime. Une centaine de requêtes prenant chacune 200 ms représente 20 secondes en exécution séquentielle. Si cela s'exécute chaque nuit via une tâche cron, 20 secondes suffisent largement, et le code concurrent est plus difficile à déboguer lorsqu'il échoue à 3 heures du matin.

Lorsque l’ordre de traitement fait partie des exigences. Certains pipelines doivent traiter les éléments dans un ordre strict, ou chaque étape dépend du résultat de la précédente. Dans ce cas, la concurrence n’est pas seulement inutile ; elle est source de bogues qui n’apparaissent qu’en cas de charge importante.

Lorsque le goulot d’étranglement se trouve ailleurs. Si ce sont les écritures dans votre base de données qui constituent la contrainte, 200 lecteurs simultanés ne font qu’allonger la file d’attente devant le même verrou. Résolvez le véritable goulot d’étranglement. La concurrence en amont d’une ressource sérielle transforme un programme lent en un programme lent souffrant d’un problème de mémoire.

Lorsque l’état partagé est complexe. Le code concurrent qui modifie un état partagé et modifiable nécessite une synchronisation, et une erreur dans celle-ci engendre la pire catégorie de bogues : intermittents, dépendants de la charge et non reproductibles sur votre machine. Si le gain de vitesse est de 2× et que l’état est complexe, un code séquentiel que vous pouvez analyser logiquement est souvent le meilleur choix technique.

Et la version qui nous concerne : si vous récupérez quelques centaines de pages par jour à partir d’un site qui s’en moque, vous n’avez besoin ni de concurrence ni de proxys. Un seul appel requests dans une boucle avec un délai de courtoisie est la bonne réponse, et nous préférons vous le dire plutôt que de vous vendre une formule dont vous n’avez pas besoin.

Questions fréquentes

Quelle est la différence la plus simple entre la concurrence et le parallélisme ?

La concurrence consiste à gérer plusieurs tâches simultanément — il s’agit d’une propriété structurelle liée à la manière dont le programme est écrit. Le parallélisme consiste à effectuer plusieurs tâches simultanément — il s’agit d’une propriété physique liée à la manière dont le programme s’exécute. La concurrence rend le parallélisme possible ; elle ne le fait pas se produire en soi.

Peut-on avoir du parallélisme sans concurrence ?

Pas de manière utile, dans le sens où nous l'entendons ici. L’exécution parallèle nécessite des unités de travail indépendantes à répartir, et c’est précisément la définition de ces unités qui constitue la concurrence. Le parallélisme au niveau matériel, tel que le SIMD, constitue une exception : il parallélise un flux d’instructions unique sur des données sans aucune structure concurrente dans votre programme.

Le GIL de Python existe-t-il toujours ?

Oui, dans la version par défaut. Depuis la version 3.13, CPython propose également une version optionnelle « free-threaded » avec le GIL désactivé, et la PEP 779 a fait évoluer cette version vers un statut officiellement pris en charge à partir de la version 3.14. La version par défaut le conserve toujours ; donc, à moins que vous n’ayez délibérément installé un interpréteur « free-threaded », le GIL est présent.

L’asynchrone est-il identique au multithreading ?

Non. La concurrence asynchrone utilise un seul thread avec une commutation coopérative à des points « await » explicites ; ainsi, une seule partie de votre code s’exécute à la fois et les commutations ne se produisent qu’aux endroits où vous les avez écrites. Le multithreading utilise plusieurs threads du système d’exploitation avec une commutation préemptive pouvant se produire n’importe où. L’asynchrone est plus facile à appréhender ; les threads peuvent atteindre un véritable parallélisme lorsque le runtime le permet.

Combien de requêtes simultanées dois-je envoyer ?

Moins que vous ne le pensez. Commencez par une dizaine, mesurez le nombre de requêtes traitées par seconde, puis augmentez ce nombre jusqu’à ce qu’il cesse de croître. La limite maximale est généralement la tolérance du serveur cible plutôt que la capacité de votre machine ; au-delà de ce seuil, une concurrence accrue génère des erreurs plutôt que d’améliorer le débit.

La concurrence rend-elle mon code plus rapide ?

Uniquement si vous êtes en attente de quelque chose. Pour les tâches liées aux E/S, les gains sont importants. Pour les tâches liées au CPU sur un seul cœur, la concurrence ralentit légèrement les choses en raison de la surcharge liée à la commutation — vous avez besoin de parallélisme, ce qui implique plusieurs cœurs et un environnement d’exécution capable de les utiliser.

Quelle est la différence entre le multiprocessing et le multithreading ?

Les threads partagent la mémoire au sein d’un même processus, ce qui rend la communication peu coûteuse mais l’état partagé dangereux. Les processus disposent d’une mémoire distincte, ce qui les rend sûrs mais rend la communication coûteuse. Dans la version par défaut de Python, les processus permettent d’obtenir un véritable parallélisme pour les tâches liées au CPU ; les threads offrent la concurrence pour les tâches liées aux E/S.

Ai-je besoin de proxys pour exécuter des requêtes concurrentes ?

Pas nécessairement. Vous en avez besoin lorsque la concurrence vous rend suffisamment visible pour qu’une cible limite le débit ou bloque l’adresse d’où vous venez. Face à une API offrant un quota généreux, ou à un site que vous êtes autorisé à explorer, la concurrence seule suffit. Face à un site qui impose des limites par adresse, la répartition devient la contrainte — et cela représente un investissement distinct de la correction de votre code.

En conclusion

Il est important de garder cette distinction à l'esprit, car elle transforme une question vague — « comment puis-je accélérer cela ? » — en une question précise dont la réponse peut être vérifiée : suis-je en attente, ou suis-je en train d'effectuer des calculs ?

Si vous êtes en attente, vous avez besoin de concurrence, et vous en avez besoin sous la forme que votre langage vous propose. Le gain est important, cela ne nécessite généralement aucun matériel supplémentaire et cette fonctionnalité est disponible dans tous les environnements d’exécution courants. Si vous effectuez des calculs, la concurrence à elle seule ne vous apportera rien et vous aurez besoin d’un véritable parallélisme — processus, threads de travail, goroutines réparties sur plusieurs cœurs, ou, dans le cas de Python, éventuellement un interpréteur à threads libres avec ses propres compromis à évaluer.

La plupart des programmes réels combinent les deux, par étapes, et l’ordre dans lequel on les traite importe davantage que le choix lui-même. Résolvez le goulot d’étranglement dominant, effectuez une nouvelle mesure et attendez-vous à ce que le résultat ait changé. Un pipeline qui était limité à 80 % par le réseau devient limité à 96 % par l’analyse syntaxique dès que vous résolvez le problème réseau, et la deuxième optimisation est un travail complètement différent de la première.