En Python, les concepts de threading et de multitraitement sont souvent abordés lors de l'optimisation des performances des applications, en particulier lorsqu'elles impliquent une exécution simultanée ou parallèle. Malgré le chevauchement des terminologies, ces deux approches sont fondamentalement différentes.
Ce blog aidera à clarifier la confusion autour du threading et du multitraitement, expliquera quand utiliser chacun et fournira des exemples pertinents pour chaque concept.
Avant de plonger dans des exemples et des cas d'utilisation, décrivons les principales différences :
Threading : fait référence à l'exécution de plusieurs threads (unités plus petites d'un processus) au sein d'un seul processus. Les threads partagent le même espace mémoire, ce qui les rend légers. Cependant, le Global Interpreter Lock (GIL) de Python limite le véritable parallélisme du threading pour les tâches liées au processeur.
Multitraitement : implique l'exécution de plusieurs processus, chacun avec son propre espace mémoire. Les processus sont plus lourds que les threads mais peuvent atteindre un véritable parallélisme car ils ne partagent pas de mémoire. Cette approche est idéale pour les tâches liées au processeur où une utilisation complète du cœur est nécessaire.
Threading est un moyen d'exécuter plusieurs tâches simultanément au sein du même processus. Ces tâches sont gérées par des threads, qui sont des unités d'exécution distinctes et légères partageant le même espace mémoire. Le threading est bénéfique pour les opérations liées aux E/S, telles que la lecture de fichiers, les requêtes réseau ou les requêtes de base de données, où le programme principal passe beaucoup de temps à attendre des ressources externes.
import threading import time def print_numbers(): for i in range(5): print(i) time.sleep(1) def print_letters(): for letter in ['a', 'b', 'c', 'd', 'e']: print(letter) time.sleep(1) # Create two threads t1 = threading.Thread(target=print_numbers) t2 = threading.Thread(target=print_letters) # Start both threads t1.start() t2.start() # Wait for both threads to complete t1.join() t2.join() print("Both threads finished execution.")
Dans l'exemple ci-dessus, deux threads s'exécutent simultanément : l'un imprime des chiffres et l'autre imprime des lettres. Les appels sleep() simulent des opérations d'E/S et le programme peut basculer entre les threads pendant ces attentes.
Le GIL de Python est un mécanisme qui empêche plusieurs threads natifs d'exécuter simultanément des bytecodes Python. Cela garantit qu'un seul thread s'exécute à la fois, même si plusieurs threads sont actifs dans le processus.
Cette limitation rend le threading inadapté aux tâches liées au processeur qui nécessitent un réel parallélisme, car les threads ne peuvent pas utiliser pleinement plusieurs cœurs en raison du GIL.
Multitraitement vous permet d'exécuter plusieurs processus simultanément, où chaque processus possède son propre espace mémoire. Étant donné que les processus ne partagent pas de mémoire, il n'y a aucune restriction GIL, permettant une véritable exécution parallèle sur plusieurs cœurs de processeur. Le multitraitement est idéal pour les tâches liées au processeur qui doivent maximiser l'utilisation du processeur.
import multiprocessing import time def print_numbers(): for i in range(5): print(i) time.sleep(1) def print_letters(): for letter in ['a', 'b', 'c', 'd', 'e']: print(letter) time.sleep(1) if __name__ == "__main__": # Create two processes p1 = multiprocessing.Process(target=print_numbers) p2 = multiprocessing.Process(target=print_letters) # Start both processes p1.start() p2.start() # Wait for both processes to complete p1.join() p2.join() print("Both processes finished execution.")
Dans cet exemple, deux processus distincts s'exécutent simultanément. Contrairement aux threads, chaque processus possède son propre espace mémoire et s'exécute indépendamment sans interférence du GIL.
Une différence clé entre le threading et le multitraitement est que les processus ne partagent pas de mémoire. Bien que cela garantisse qu'il n'y a aucune interférence entre les processus, cela signifie également que le partage de données entre eux nécessite des mécanismes spéciaux, tels que des objets Queue, Pipe ou Manager fournis par le module multitraitement.
Maintenant que nous comprenons comment fonctionnent les deux approches, voyons quand choisir le threading ou le multitraitement en fonction du type de tâches :
Use Case | Type | Why? |
---|---|---|
Network requests, I/O-bound tasks (file read/write, DB calls) | Threading | Multiple threads can handle I/O waits concurrently. |
CPU-bound tasks (data processing, calculations) | Multiprocessing | True parallelism is possible by utilizing multiple cores. |
Task requires shared memory or lightweight concurrency | Threading | Threads share memory and are cheaper in terms of resources. |
Independent tasks needing complete isolation (e.g., separate processes) | Multiprocessing | Processes have isolated memory, making them safer for independent tasks. |
Threading excels in scenarios where the program waits on external resources (disk I/O, network). Since threads can work concurrently during these wait times, threading can help boost performance.
However, due to the GIL, CPU-bound tasks do not benefit much from threading because only one thread can execute at a time.
Multiprocessing allows true parallelism by running multiple processes across different CPU cores. Each process runs in its own memory space, bypassing the GIL and making it ideal for CPU-bound tasks.
However, creating processes is more resource-intensive than creating threads, and inter-process communication can slow things down if there's a lot of data sharing between processes.
Let's compare threading and multiprocessing for a CPU-bound task like calculating the sum of squares for a large list.
import threading def calculate_squares(numbers): result = sum([n * n for n in numbers]) print(result) numbers = range(1, 10000000) t1 = threading.Thread(target=calculate_squares, args=(numbers,)) t2 = threading.Thread(target=calculate_squares, args=(numbers,)) t1.start() t2.start() t1.join() t2.join()
Due to the GIL, this example will not see significant performance improvements over a single-threaded version because the threads can't run simultaneously for CPU-bound operations.
import multiprocessing def calculate_squares(numbers): result = sum([n * n for n in numbers]) print(result) if __name__ == "__main__": numbers = range(1, 10000000) p1 = multiprocessing.Process(target=calculate_squares, args=(numbers,)) p2 = multiprocessing.Process(target=calculate_squares, args=(numbers,)) p1.start() p2.start() p1.join() p2.join()
In the multiprocessing example, you'll notice a performance boost since both processes run in parallel across different CPU cores, fully utilizing the machine's computational resources.
Understanding the difference between threading and multiprocessing is crucial for writing efficient Python programs. Here’s a quick recap:
Knowing when to use which approach can lead to significant performance improvements and efficient use of resources.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!