Heim >Backend-Entwicklung >Python-Tutorial >Parallelität in Python mit Threading und Multiprocessing

Parallelität in Python mit Threading und Multiprocessing

DDD
DDDOriginal
2024-09-14 06:23:121092Durchsuche

Concurrency in Python with Threading and Multiprocessing

Parallelität ist ein entscheidender Gedanke in der modernen Programmierung, der die gleichzeitige Ausführung mehrerer Aufgaben ermöglicht, um die Leistung von Anwendungen zu verbessern.

Es gibt mehrere Möglichkeiten, Parallelität in Python zu erreichen, wobei Threading und Multiprocessing die bekanntesten sind.

In diesem Artikel werden wir diese beiden Methoden im Detail untersuchen, verstehen, wie sie funktionieren, und besprechen, wann sie jeweils verwendet werden sollten, zusammen mit praktischen Codebeispielen.


Was ist Parallelität?

Bevor wir über Threading und Multiprocessing sprechen, ist es wichtig zu verstehen, was Parallelität bedeutet.

Parallelität liegt vor, wenn ein Programm mehrere Aufgaben oder Prozesse gleichzeitig ausführen kann.

Dadurch kann das Programm die Ressourcen besser nutzen und schneller laufen, insbesondere wenn es beispielsweise Dateien lesen oder viele Berechnungen durchführen muss.

Es gibt zwei Hauptmethoden, um Parallelität zu erreichen:

  • Parallelität: Mehrere Aufgaben gleichzeitig auf verschiedenen Teilen des Computerprozessors ausführen.
  • Parallelität: Bearbeitung mehrerer Aufgaben im gleichen Zeitraum, aber nicht unbedingt im exakt gleichen Moment.

Python bietet zwei Hauptmöglichkeiten, um Parallelität zu erreichen:

  • Threading: Für Aufgaben, die gleichzeitig verwaltet werden können.
  • Multiprocessing: Für Aufgaben, die wirklich gleichzeitig auf verschiedenen Prozessorkernen ausgeführt werden müssen.

Threading in Python

Threading ermöglicht es Ihnen, mehrere kleinere Einheiten eines Prozesses, sogenannte Threads, innerhalb desselben Prozesses auszuführen und sich denselben Speicherplatz zu teilen.

Threads sind leichter als Prozesse und der Wechsel zwischen ihnen ist schneller.

Threading in Python unterliegt jedoch der Global Interpreter Lock (GIL), die sicherstellt, dass jeweils nur ein Thread Python-Code ausführen kann.

So funktioniert das Einfädeln

Das Threading-Modul von Python bietet eine einfache und flexible Möglichkeit, Threads zu erstellen und zu verwalten.

Beginnen wir mit einem einfachen Beispiel:

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

In diesem Beispiel:

  • Wir definieren eine Funktion print_numbers(), die Zahlen von 0 bis 4 mit einer Verzögerung von einer Sekunde zwischen den Drucken druckt.
  • Wir erstellen einen Thread mit threading.Thread() und übergeben print_numbers() als Zielfunktion.
  • Die start()-Methode startet die Ausführung des Threads und join() stellt sicher, dass das Hauptprogramm auf das Ende des Threads wartet, bevor es fortfährt.

Beispiel: Threading für E/A-gebundene Aufgaben

Threading ist besonders nützlich für E/A-gebundene Aufgaben, wie Dateioperationen, Netzwerkanfragen oder Datenbankabfragen, bei denen das Programm die meiste Zeit damit verbringt, auf externe Ressourcen zu warten.

Hier ist ein Beispiel, das das Herunterladen von Dateien mithilfe von Threads simuliert:

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.

Durch die Erstellung und Verwaltung separater Threads für jeden Dateidownload kann das Programm mehrere Aufgaben gleichzeitig bearbeiten und so die Gesamteffizienz verbessern.

Die wichtigsten Schritte im Code sind wie folgt:

  • Eine Funktion download_file ist definiert, um den Downloadvorgang zu simulieren.
  • Eine Liste mit Dateinamen wird erstellt, um die Dateien darzustellen, die heruntergeladen werden müssen.
  • Für jede Datei in der Liste wird ein neuer Thread mit „download_file“ als Zielfunktion erstellt. Jeder Thread wird sofort nach der Erstellung gestartet und zu einer Threadliste hinzugefügt.
  • Das Hauptprogramm wartet mit der Methode „join()“ auf den Abschluss aller Threads und stellt so sicher, dass das Programm erst dann fortfährt, wenn alle Downloads abgeschlossen sind.

Einschränkungen beim Einfädeln

Threading kann zwar die Leistung für I/O-gebundene Aufgaben verbessern, es weist jedoch Einschränkungen auf:

  • Global Interpreter Lock (GIL): Die GIL beschränkt die Ausführung von CPU-gebundenen Aufgaben auf jeweils einen Thread und begrenzt so die Effektivität des Threadings in Mehrkernprozessoren.
  • Race Conditions: Da Threads denselben Speicherplatz nutzen, kann eine unsachgemäße Synchronisierung zu Race Conditions führen, bei denen das Ergebnis eines Programms vom Timing der Threads abhängt.
  • Deadlocks: Threads, die aufeinander warten, um Ressourcen freizugeben, können zu Deadlocks führen, bei denen kein Fortschritt erzielt wird.

Multiverarbeitung in Python

Multiprocessing beseitigt die Einschränkungen des Threadings, indem es separate Prozesse anstelle von Threads verwendet.

Jeder Prozess verfügt über seinen eigenen Speicherplatz und Python-Interpreter, was echte Parallelität auf Multi-Core-Systemen ermöglicht.

Dadurch eignet sich Multiprocessing ideal für Aufgaben, die einen hohen Rechenaufwand erfordern.

So funktioniert Multiprocessing

Mit dem Multiprocessing-Modul in Python können Sie Prozesse einfach erstellen und verwalten.

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.

Ob Sie Daten verarbeiten, mehrere Netzwerkanfragen bearbeiten oder komplexe Berechnungen durchführen, die Threading- und Multiprocessing-Tools von Python bieten Ihnen alles, was Sie brauchen, um Ihr Programm so effizient und schnell wie möglich zu machen.

Das obige ist der detaillierte Inhalt vonParallelität in Python mit Threading und Multiprocessing. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn