Maison  >  Article  >  développement back-end  >  Concurrence en Python avec threading et multitraitement

Concurrence en Python avec threading et multitraitement

DDD
DDDoriginal
2024-09-14 06:23:121066parcourir

Concurrency in Python with Threading and Multiprocessing

La concurrence est une idée cruciale dans la programmation moderne qui permet à plusieurs tâches de s'exécuter en même temps pour améliorer les performances des applications.

Il existe plusieurs façons d'obtenir la concurrence en Python, le threading et le multitraitement étant les plus connus.

Dans cet article, nous explorerons ces deux méthodes en détail, comprendrons comment elles fonctionnent et discuterons du moment où les utiliser, ainsi que des exemples de code pratiques.


Qu’est-ce que la concurrence ?

Avant de parler de threading et de multitraitement, il est important de comprendre ce que signifie la concurrence.

La simultanéité, c'est lorsqu'un programme peut effectuer plusieurs tâches ou processus en même temps.

Cela peut permettre au programme de mieux utiliser les ressources et de s'exécuter plus rapidement, en particulier lorsqu'il doit effectuer des tâches telles que lire des fichiers ou effectuer de nombreux calculs.

Il existe deux manières principales d'obtenir la simultanéité :

  • Parallélisme : Exécution de plusieurs tâches exactement en même temps sur différentes parties du processeur de l'ordinateur.
  • Concurrency : gérer plusieurs tâches au cours de la même période, mais pas nécessairement exactement au même moment.

Python propose deux manières principales d'obtenir la simultanéité :

  • Threading : Pour les tâches pouvant être gérées en même temps.
  • Multitraitement : pour les tâches qui doivent s'exécuter véritablement simultanément sur différents cœurs de processeur.

Threading en Python

Le threading vous permet d'exécuter plusieurs unités plus petites d'un processus, appelées threads, au sein du même processus, partageant le même espace mémoire.

Les threads sont plus légers que les processus et la commutation entre eux est plus rapide.

Cependant, le threading en Python est soumis au Global Interpreter Lock (GIL), qui garantit qu'un seul thread peut exécuter du code Python à la fois.

Comment fonctionne le threading

Le module de threading de Python offre un moyen simple et flexible de créer et de gérer des threads.

Commençons par un exemple simple :

import threading
import time


def print_numbers():
    for i in range(5):
        print(f"Number: {i}")
        time.sleep(1)


# Creating a thread
thread = threading.Thread(target=print_numbers)

# Starting the thread
thread.start()

# Wait for the thread to complete
thread.join()

print("Thread has finished executing")


# Output:
# Number: 0
# Number: 1
# Number: 2
# Number: 3
# Number: 4
# Thread has finished executing

Dans cet exemple :

  • Nous définissons une fonction print_numbers() qui imprime les nombres de 0 à 4 avec un délai d'une seconde entre les impressions.
  • Nous créons un fil de discussion en utilisant threading.Thread() et passons print_numbers() comme fonction cible.
  • La méthode start() commence l'exécution du thread et join() garantit que le programme principal attend la fin du thread avant de continuer.

Exemple : Threading pour les tâches liées aux E/S

Le threading est particulièrement utile pour les tâches liées aux E/S, telles que les opérations sur les fichiers, les requêtes réseau ou les requêtes de base de données, où le programme passe la plupart de son temps à attendre des ressources externes.

Voici un exemple qui simule le téléchargement de fichiers à l'aide de threads :

import threading
import time


def download_file(file_name):
    print(f"Starting download of {file_name}...")
    time.sleep(2)  # Simulate download time
    print(f"Finished downloading {file_name}")


files = ["file1.zip", "file2.zip", "file3.zip"]

threads = []

# Create and start threads
for file in files:
    thread = threading.Thread(target=download_file, args=(file,))
    thread.start()
    threads.append(thread)

# Ensure all threads have finished
for thread in threads:
    thread.join()

print("All files have been downloaded.")

# Output:
# Starting download of file1.zip...
# Starting download of file2.zip...
# Starting download of file3.zip...
# Finished downloading file1.zip
# Finished downloading file2.zip
# Finished downloading file3.zip
# All files have been downloaded.

En créant et en gérant des threads distincts pour chaque téléchargement de fichier, le programme peut gérer plusieurs tâches simultanément, améliorant ainsi l'efficacité globale.

Les étapes clés du code sont les suivantes :

  • Une fonction download_file est définie pour simuler le processus de téléchargement.
  • Une liste de noms de fichiers est créée pour représenter les fichiers qui doivent être téléchargés.
  • Pour chaque fichier de la liste, un nouveau fil de discussion est créé avec download_file comme fonction cible. Chaque fil de discussion est démarré immédiatement après sa création et ajouté à une liste de fils de discussion.
  • Le programme principal attend la fin de tous les threads en utilisant la méthode join() , garantissant que le programme ne continue pas tant que tous les téléchargements ne sont pas terminés.

Limites du filetage

Bien que le threading puisse améliorer les performances des tâches liées aux E/S, il présente des limites :

  • Global Interpreter Lock (GIL) : Le GIL restreint l'exécution à un thread à la fois pour les tâches liées au processeur, limitant l'efficacité du threading dans les processeurs multicœurs.
  • Conditions de concurrence : Étant donné que les threads partagent le même espace mémoire, une synchronisation incorrecte peut conduire à des conditions de concurrence, dans lesquelles le résultat d'un programme dépend du timing des threads.
  • Deadlocks : Les threads qui s'attendent les uns les autres pour libérer des ressources peuvent conduire à des blocages, dans lesquels aucun progrès n'est réalisé.

Multitraitement en Python

Le multitraitement résout les limites du threading en utilisant des processus distincts au lieu de threads.

Chaque processus possède son propre espace mémoire et son interpréteur Python, permettant un véritable parallélisme sur les systèmes multicœurs.

Cela rend le multitraitement idéal pour les tâches qui nécessitent des calculs lourds.

Comment fonctionne le multitraitement

Le module multitraitement en Python vous permet de créer et de gérer facilement des processus.

Let’s start with a basic example:

import multiprocessing
import time


def print_numbers():
    for i in range(5):
        print(f"Number: {i}")
        time.sleep(1)


if __name__ == "__main__":
    # Creating a process
    process = multiprocessing.Process(target=print_numbers)

    # Starting the process
    process.start()

    # Wait for the process to complete
    process.join()

    print("Process has finished executing")

# Output:
# Number: 0
# Number: 1
# Number: 2
# Number: 3
# Number: 4
# Process has finished executing

This example is similar to the threading example, but with processes.

Notice that the process creation and management are similar to threading, but because processes run in separate memory spaces, they are truly concurrent and can run on different CPU cores.

Example: Multiprocessing for CPU-Bound Tasks

Multiprocessing is particularly beneficial for tasks that are CPU-bound, such as numerical computations or data processing.

Here’s an example that calculates the square of numbers using multiple processes:

import multiprocessing


def compute_square(number):
    return number * number


if __name__ == "__main__":
    numbers = [1, 2, 3, 4, 5]

    # Create a pool of processes
    with multiprocessing.Pool() as pool:
        # Map function to numbers using multiple processes
        results = pool.map(compute_square, numbers)

    print("Squares:", results)

# Output:
# Squares: [1, 4, 9, 16, 25]

Here are the key steps in the code:

  • A function compute_square is defined to take a number as input and return its square.
  • The code within the if name == "main": block ensures that it runs only when the script is executed directly.
  • A list of numbers is defined, which will be squared.
  • A pool of worker processes is created using multiprocessing.Pool().
  • The map method is used to apply the compute_square function to each number in the list, distributing the workload across multiple processes.

Inter-Process Communication (IPC)

Since each process has its own memory space, sharing data between processes requires inter-process communication (IPC) mechanisms.

The multiprocessing module provides several tools for IPC, such as Queue, Pipe, and Value.

Here’s an example using Queue to share data between processes:

import multiprocessing


def worker(queue):
    # Retrieve and process data from the queue
    while not queue.empty():
        item = queue.get()
        print(f"Processing {item}")


if __name__ == "__main__":
    queue = multiprocessing.Queue()

    # Add items to the queue
    for i in range(10):
        queue.put(i)

    # Create a pool of processes to process the queue
    processes = []
    for _ in range(4):
        process = multiprocessing.Process(target=worker, args=(queue,))
        processes.append(process)
        process.start()

    # Wait for all processes to complete
    for process in processes:
        process.join()

    print("All processes have finished.")


# Output:
# Processing 0
# Processing 1
# Processing 2
# Processing 3
# Processing 4
# Processing 5
# Processing 6
# Processing 7
# Processing 8
# Processing 9
# All processes have finished.

In this example:

  • def worker(queue): Defines a function worker that takes a queue as an argument. The function retrieves and processes items from the queue until it is empty.
  • if name == "main":: Ensures that the following code runs only if the script is executed directly, not if it is imported as a module.
  • queue = multiprocessing.Queue(): Creates a queue object for inter-process communication.
  • for i in range(10): queue.put(i): Adds items (numbers 0 through 9) to the queue.
  • processes = []: Initializes an empty list to store process objects.
  • The for loop for _ in range(4): Creates four worker processes.
  • process = multiprocessing.Process(target=worker, args=(queue,)): Creates a new process with worker as the target function and passes the queue as an argument.
  • processes.append(process): Adds the process object to the processes list.
  • process.start(): Starts the process.
  • The for loop for process in processes: Waits for each process to complete using the join() method.

Challenges of Multiprocessing

While multiprocessing provides true parallelism, it comes with its own set of challenges:

  • Higher Overhead: Creating and managing processes is more resource-intensive than threads due to separate memory spaces.
  • Complexity: Communication and synchronization between processes are more complex than threading, requiring IPC mechanisms.
  • Memory Usage: Each process has its own memory space, leading to higher memory usage compared to threading.

When to Use Threading vs. Multiprocessing

Choosing between threading and multiprocessing depends on the type of task you're dealing with:

Use Threading:

  • For tasks that involve a lot of waiting, such as network operations or reading/writing files (I/O-bound tasks).
  • When you need to share memory between tasks and can manage potential issues like race conditions.
  • For lightweight concurrency without the extra overhead of creating multiple processes.

Use Multiprocessing:

  • For tasks that require heavy computations or data processing (CPU-bound tasks) and can benefit from running on multiple CPU cores at the same time.
  • When you need true parallelism and the Global Interpreter Lock (GIL) in threading becomes a limitation.
  • For tasks that can run independently and don’t require frequent communication or shared memory.

Conclusion

Concurrency in Python is a powerful way to make your applications run faster.

Threading is great for tasks that involve a lot of waiting, like network operations or reading/writing files, but it's not as effective for tasks that require heavy computations because of something called the Global Interpreter Lock (GIL).

On the other hand, multiprocessing allows for true parallelism, making it perfect for CPU-intensive tasks, although it comes with higher overhead and complexity.

Que vous traitiez des données, traitiez plusieurs requêtes réseau ou effectuiez des calculs complexes, les outils de threading et de multitraitement de Python vous offrent ce dont vous avez besoin pour rendre votre programme aussi efficace et rapide que possible.

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn