Ce blog abordera en détail la concurrence et le parallélisme afin de vous aider à choisir le concept le mieux adapté à votre application.
Qu’est-ce que la concurrence ?
En termes simples, la concurrence est un concept utilisé dans le développement de logiciels pour gérer plusieurs tâches simultanément. Cependant, en théorie, elle n’exécute pas toutes les tâches exactement au même moment. Elle permet plutôt au système ou à l’application de gérer plusieurs tâches simultanément en passant rapidement de l’une à l’autre, créant ainsi une illusion de traitement parallèle. Ce processus est également connu sous le nom d’entrelacement de tâches.
Prenons l’exemple d’un serveur web qui doit traiter plusieurs requêtes d’utilisateurs.
- L’utilisateur 1 envoie une requête au serveur pour récupérer des données.
- L’utilisateur 2 envoie une requête au serveur pour télécharger un fichier.
- L’utilisateur 3 envoie une requête au serveur pour récupérer des images.
Sans concurrence, chaque utilisateur doit attendre que la demande précédente soit traitée.
- Étape 1: le processeur commence à traiter la demande de récupération de données dans le thread 1.
- Étape 2: pendant que le thread 1 attend le résultat, le CPU lance le processus de téléchargement du fichier dans le thread 2.
- Étape 3: pendant que le thread 2 attend que le fichier soit téléchargé, le CPU commence la récupération des images dans le thread 3.
- Étape 4: Ensuite, le CPU bascule entre ces 3 threads en fonction de la disponibilité des ressources afin de terminer les 3 tâches simultanément.

Par rapport à l’approche d’exécution synchrone, l’approche de concurrence est beaucoup plus rapide et extrêmement utile pour les environnements monocœur afin d’améliorer le temps de réponse global du système, l’utilisation des ressources et les capacités de débit du système. Cependant, la concurrence ne se limite pas aux monocœurs ; elle peut également être mise en œuvre dans des environnements multicœurs.
Cas d’utilisation de la concurrence
- Interfaces utilisateur réactives.
- Serveurs web.
- Systèmes en temps réel.
- Réseaux et opérations d’E/S.
- Traitement en arrière-plan.
Différents modèles de concurrence
Face à la complexité et aux exigences croissantes des applications modernes, les développeurs ont introduit de nouveaux modèles de concurrence afin de pallier les lacunes de l’approche traditionnelle. Voici quelques modèles de concurrence clés et leurs utilisations :
1. Multitâche coopératif
Dans ce modèle, les tâches cèdent volontairement le contrôle au planificateur à des moments appropriés, lui permettant ainsi de traiter d’autres tâches. Ce céder la place se produit souvent lorsque la tâche est inactive ou en attente d’opérations d’E/S. C’est l’un des modèles les plus faciles à mettre en œuvre, car le changement de contexte est géré dans le code de l’application.
Exemples:
- Systèmes embarqués légers
- Premières versions de Microsoft Windows (Windows 3.x)
- Mac OS classique
Applications concrètes :
- Applications utilisant des coroutines telles que Python asyncio et Kotlin coroutines.
2. Multitâche préemptif
Le système d’exploitation ou le planificateur d’exécution force les tâches à s’arrêter et alloue du temps CPU à d’autres tâches en fonction d’un algorithme de planification. Ce modèle garantit que toutes les tâches bénéficient d’un temps CPU égal. Cependant, il nécessite un changement de contexte plus complexe.
Exemples:
- Threads Java gérés par la JVM.
- Module de threading Python.
Applications concrètes :
- Systèmes d’exploitation modernes (Windows, macOS, Linux)
- Serveurs Web.
3. Concurrence événementielle
Dans ce modèle, les tâches sont divisées en petites opérations non bloquantes et mises en file d’attente. Elles sont ensuite récupérées dans la file d’attente, exécutées, puis remplacées par les suivantes, ce qui permet au système de rester interactif.
Exemples:
- Node.js (environnement d’exécution JavaScript).
- Modèle async/await de JavaScript.
- Bibliothèque asyncio de Python.
Applications concrètes :
- Serveurs web tels que Node.js.
- Applications de chat en temps réel.
4. Modèle acteur
Utilise des acteurs pour envoyer et recevoir des messages de manière asynchrone. Chaque acteur traite un message à la fois, ce qui évite le partage d’état et réduit le besoin de verrous.
Exemples:
- Cadre Akka (Java/Scala).
- Langage de programmationErlang.
- Microsoft Orleans (applications .NET distribuées).
Applications concrètes:
- Systèmes distribués.
- Systèmes de télécommunications.
- Systèmes de traitement de données en temps réel.
5. Programmation réactive
Ce modèle vous permet de créer des flux de données (observables) et de définir comment ils doivent être traités (opérateurs) et comment y réagir (observateurs). Les modifications de données ou les événements se produisent et se propagent automatiquement à travers les flux vers tous les observateurs abonnés. Cette approche facilite la gestion des données et des événements asynchrones, offrant un moyen clair et déclaratif de traiter des flux de données complexes.
Exemples:
Applications concrètes:
- Pipelines de traitement des données en temps réel.
- Interfaces utilisateur interactives.
- Applications nécessitant un traitement dynamique et réactif des données.
Qu’est-ce que le parallélisme ?
Le parallélisme est un autre concept populaire utilisé dans le développement logiciel pour traiter plusieurs tâches simultanément. Contrairement à la concurrence, qui crée l’illusion d’un traitement parallèle en passant rapidement d’une tâche à l’autre, le parallélisme exécute réellement plusieurs tâches simultanément à l’aide de plusieurs cœurs de processeur ou processeurs. Il consiste à diviser les tâches plus importantes en sous-tâches plus petites et indépendantes qui peuvent être exécutées en parallèle. Ce processus est appelé décomposition des tâches.
Prenons l’exemple d’une application de traitement de données qui génère des rapports après avoir effectué des analyses et des simulations. Sans parallélisme, cette opération s’exécuterait comme une seule grande tâche, ce qui prendrait beaucoup de temps. Mais si vous optez pour le parallélisme, la tâche sera accomplie beaucoup plus rapidement grâce à la décomposition des tâches.
Voici comment fonctionne le parallélisme :
- Étape 1: divisez la tâche principale en sous-tâches indépendantes. Ces sous-tâches doivent pouvoir s’exécuter sans attendre les entrées provenant d’autres tâches. Cependant, s’il existe des dépendances, vous devez les planifier en conséquence afin de vous assurer qu’elles sont exécutées dans le bon ordre. Dans cet exemple, je suppose qu’il n’y a aucune dépendance entre les sous-tâches.
- Sous-tâche 1: effectuer l’analyse des données.
- Sous-tâche 2: Génération de rapports.
- Sous-tâche 3: exécution de simulations.
- Étape 2: affecter les 3 sous-tâches à 3 cœurs.
- Étape 3: Enfin, combinez les résultats de chaque sous-tâche pour obtenir le résultat final de la tâche d’origine.

Cas d’utilisation du parallélisme
- Calculs scientifiques et simulations.
- Traitement des données.
- Traitement d’images.
- Apprentissage automatique.
- Analyse des risques.
Différents modèles de parallélisme
Tout comme la concurrence, le parallélisme comporte également plusieurs modèles différents permettant d’utiliser efficacement les processeurs multicœurs et les ressources informatiques distribuées. Voici quelques modèles de parallélisme clés et leurs utilisations :
1. Parallélisme des données
Ce modèle distribue les données sur plusieurs processeurs et effectue simultanément la même opération sur chaque sous-ensemble de données. Il est particulièrement efficace pour les tâches qui peuvent être facilement divisées en sous-tâches indépendantes.
Exemples:
- OpérationsSIMD (Single Instruction, Multiple Data).
- Traitement parallèle de tableaux.
- Cadre MapReduce.
Applications concrètes:
- Traitement d’images et de signaux
- Analyse de données à grande échelle
- Simulations scientifiques
2. Parallélisme des tâches
Le parallélisme des tâches consiste à diviser la tâche globale en tâches plus petites et indépendantes qui peuvent être exécutées simultanément sur différents processeurs. Chaque tâche effectue une opération différente.
Exemples:
- Parallélisme basé sur les threads en Java.
- Tâches parallèles dans .NET.
- Threads POSIX.
Applications concrètes:
- Serveurs Web traitant plusieurs requêtes client.
- Implémentations d’algorithmes parallèles.
- Systèmes de traitement en temps réel.
3. Parallélisme en pipeline
Dans le parallélisme en pipeline, les tâches sont divisées en étapes, et chaque étape est traitée en parallèle. Les données circulent dans le pipeline, chaque étape fonctionnant simultanément.
Exemples:
- Commandes pipeline Unix.
- Pipelines de traitement d’images.
- Pipelines de traitement des données dans les outils ETL (Extract, Transform, Load).
Applications concrètes:
- Traitement vidéo et audio.
- Applications de streaming de données en temps réel.
- Automatisation de la fabrication et des chaînes de montage.
4. Modèle Fork/Join
Ce modèle consiste à diviser une tâche en sous-tâches plus petites (fork), à les exécuter en parallèle, puis à combiner les résultats (join). Il est utile pour les algorithmes de division et de conquête.
Exemples:
- Cadre Fork/Join en Java.
- Algorithmes récursifs parallèles (par exemple, tri par fusion parallèle).
- Intel Threading Building Blocks (TBB).
Applications concrètes:
- Tâches de calcul complexes telles que le tri de grands jeux de données.
- Algorithmes récursifs.
- Calculs scientifiques à grande échelle.
5. Parallélisme GPU
Le parallélisme GPU exploite les capacités de traitement massivement parallèle des processeurs graphiques (GPU) pour exécuter simultanément des milliers de threads, ce qui le rend idéal pour les tâches hautement parallèles.
Exemples:
- CUDA (Compute Unified Device Architecture) de NVIDIA.
- OpenCL (Open Computing Language).
- TensorFlow pour l’apprentissage profond.
Applications concrètes:
- Apprentissage automatique et apprentissage profond.
- Rendu graphique en temps réel.
- Calcul scientifique haute performance.
Concurrence et parallélisme
Maintenant que vous comprenez bien comment fonctionnent la concurrence et le parallélisme, comparons-les sous plusieurs aspects afin de voir comment tirer le meilleur parti des deux.
1. Utilisation des ressources
- Concurrence: exécute plusieurs tâches au sein d’un même cœur, en partageant les ressources entre les tâches. Par exemple, le processeur bascule entre les tâches pendant les périodes d’inactivité ou d’attente.
- Parallélisme: utilise plusieurs cœurs ou processeurs pour exécuter des tâches simultanément.
2. Objectif
- Concurrence: se concentre sur la gestion de plusieurs tâches en même temps.
- Parallélisme: se concentre sur l’exécution simultanée de plusieurs tâches.
3. Exécution des tâches
- Concurrence: les tâches sont exécutées de manière entrelacée. Le changement rapide de contexte du processeur crée une illusion d’exécution parallèle.
- Parallélisme: les tâches sont exécutées de manière véritablement parallèle sur différents processeurs ou cœurs.
4. Changement de contexte
- Concurrence: des changements de contexte fréquents se produisent lorsque le CPU passe d’une tâche à l’autre pour donner l’impression d’une exécution simultanée. Cela peut parfois avoir un impact négatif sur les performances si les tâches sont fréquemment inactives.
- Parallélisme: changement de contexte minimal ou inexistant, car les tâches s’exécutent sur des cœurs ou des processeurs distincts.
5. Cas d’utilisation
- Concurrence: tâches liées aux E/S telles que les E/S disque, la communication réseau ou les entrées utilisateur.
- Parallélisme: tâches liées au CPU qui nécessitent un traitement intensif, telles que les calculs mathématiques, l’analyse de données et le traitement d’images.

Peut-on utiliser conjointement la concurrence et le parallélisme ?
D’après la comparaison ci-dessus, nous pouvons constater que la concurrence et le parallélisme se complètent dans de nombreuses situations. Mais avant de passer à des exemples concrets, voyons comment cette combinaison fonctionne dans les coulisses d’un environnement multicœur. Pour cela, prenons l’exemple d’un serveur web qui effectue la lecture, l’écriture et l’analyse de données.
Étape 1 : identification des tâches
Tout d’abord, vous devez identifier les tâches liées aux E/S et celles liées au CPU dans votre application. Dans ce cas :
- Liées à l’E/S : lecture et écriture de données.
- Liées au CPU : analyse des données.
Étape 2 : exécution concurrente
Les tâches de lecture et d’écriture de données peuvent être exécutées dans des threads distincts au sein d’un même cœur, car il s’agit de tâches liées aux E/S. Le serveur utilise une boucle d’événements pour gérer ces tâches et bascule rapidement entre les threads, entrecoupant l’exécution des tâches. Vous pouvez utiliser une bibliothèque de programmation asynchrone telle que Python asyncio pour implémenter ce comportement de concurrence.
Étape 3 : Exécution parallèle
Plusieurs cœurs peuvent être affectés à des tâches liées au CPU afin de les traiter en parallèle. Dans ce cas, l’analyse des données peut être divisée en plusieurs sous-tâches et chaque sous-tâche sera exécutée dans un cœur indépendant. Vous pouvez utiliser un framework d’exécution parallèle tel que Python concurrent.futures pour implémenter ce comportement.
Étape 4 : Synchronisation et coordination
Parfois, les threads s’exécutant sur différents cœurs peuvent dépendre les uns des autres. Dans de telles situations, des mécanismes de synchronisation tels que les verrous et les sémaphores sont nécessaires pour garantir l’intégrité des données et éviter les conditions de concurrence.

L’extrait de code ci-dessous montre comment utiliser la concurrence et le parallélisme dans la même application à l’aide de Python :
import asyncio
from concurrent.futures import ProcessPoolExecutor
import os
# Simuler une tâche liée à l'E/S (lecture de données)
async def read_data():
await asyncio.sleep(1) # Simuler un délai d'E/S
data = [1, 2, 3, 4, 5] # Données factices
print("Lecture des données terminée")
return data
# Simuler une tâche liée à l'E/S (écriture de données)
async def write_data(data):
await asyncio.sleep(1) # Simuler un délai d'E/S
print(f"Écriture des données terminée : {data}")
# Simuler une tâche liée au CPU (analyse des données)
def analyze_data(data):
print(f"Analyse des données lancée sur le CPU : {os.getpid()}")
result = [x ** 2 for x in data] # Simuler le calcul
print(f"Analyse des données terminée sur le CPU : {os.getpid()}")
return result
async def handle_request():
# Concurrence : lecture asynchrone des données
data = await read_data()
# Parallélisme : analyse parallèle des données
loop = asyncio.get_event_loop()
with ProcessPoolExecutor() as executor:
analyzed_data = await loop.run_in_executor(executor, analyze_data, data)
# Concurrence : écrire les données de manière asynchrone
await write_data(analyzed_data)
async def main():
# Simuler le traitement de plusieurs requêtes
await asyncio.gather(handle_request(), handle_request())
# Exécuter le serveur
asyncio.run(main())
Exemples concrets de combinaison de la concurrence et du parallélisme
Voyons maintenant quelques cas d’utilisation courants dans lesquels nous pouvons combiner la concurrence et le parallélisme pour obtenir des performances optimales.
1. Traitement des données financières
Les principales tâches d’un système de traitement des données financières comprennent la collecte, le traitement et l’analyse des données tout en assurant les opérations quotidiennes.
- La concurrence est utilisée pour extraire des données financières de diverses ressources, telles que le marché boursier, à l’aide d’opérations d’E/S asynchrones.
- Analyse des données collectées pour générer des rapports. Il s’agit d’une tâche qui sollicite fortement le processeur, et le parallélisme est utilisé pour l’exécuter en parallèle sans affecter les opérations quotidiennes.
2. Traitement vidéo
Les principales tâches d’un système de traitement vidéo comprennent le téléchargement, l’encodage/décodage et l’analyse de fichiers vidéo.
- La concurrence peut être utilisée pour traiter plusieurs demandes de téléchargement de vidéos à l’aide d’opérations d’E/S asynchrones. Cela permet aux utilisateurs de télécharger des vidéos sans attendre la fin des autres téléchargements.
- Le parallélisme est utilisé pour les tâches gourmandes en ressources CPU telles que l’encodage, le décodage et l’analyse des fichiers vidéo.
3. Extraction de données
Les principales tâches d’un service de scraping de données comprennent la récupération de données à partir de divers sites web et l’analyse des données collectées afin d’en tirer des informations utiles.
- La récupération des données peut être gérée à l’aide de la concurrence. Cela garantit que la collecte de données est efficace et ne bloque pas pendant l’attente des réponses.
- Le parallélisme est utilisé pour traiter les données collectées sur plusieurs cœurs de processeur. Il améliore le processus décisionnel de l’organisation en fournissant des rapports en temps réel.
Conclusion
La concurrence et le parallélisme sont deux concepts clés utilisés dans le développement de logiciels pour améliorer les performances des applications. La concurrence permet d’exécuter plusieurs tâches simultanément, tandis que le parallélisme accélère le traitement des données en utilisant plusieurs cœurs de processeur. Bien qu’ils aient des fonctionnalités distinctes, leur intégration peut améliorer considérablement les performances des applications avec des tâches liées à l’E/S et au processeur.
Les outils de Bright Data, tels que les API Web Scraper, les fonctions Web Scraper et le Navigateur de scraping, sont conçus pour exploiter pleinement ces techniques et vous aider à relever les défis courants du Scraping web. Ils utilisent des opérations asynchrones pour collecter simultanément des données provenant de plusieurs sources et un traitement parallèle pour analyser et organiser rapidement les données. Ainsi, choisir un fournisseur de données comme Bright Data, qui a déjà intégré la concurrence et le parallélisme dans son cœur, peut vous faire gagner du temps et vous épargner des efforts, car vous n’aurez pas besoin de mettre en œuvre ces concepts à partir de zéro lors du Scraping web.
Commencez votre essai gratuit dès aujourd’hui !