Fortgeschrittene Python Concurrency: Multithreading und AsyncIO
Min-jun Kim
Dev Intern · Leapcell

Erforschung der Python Concurrent Programmierung
In der Python-Programmierung ist Multithreading ein häufig verwendetes Mittel der gleichzeitigen Programmierung, das die Ausführungseffizienz des Programms effektiv verbessern kann, insbesondere bei I/O-intensiven Aufgaben. Python macht Multithreading-Programmierung mit Hilfe des threading
-Moduls relativ einfach. Dieser Artikel wird sich eingehend mit dem grundlegenden Wissen des threading
-Moduls befassen und die Anwendung von Multithreading anhand von Beispielen demonstrieren.
1. Grundlegende Konzepte des Multithreading
Bevor wir beginnen, wollen wir zunächst einige grundlegende Konzepte der Multithreading-Programmierung verstehen:
- Thread: Es ist die kleinste Einheit für das Betriebssystem, um die Operationsplanung durchzuführen, die normalerweise innerhalb eines Prozesses existiert.
- Multithreading: Es bezieht sich auf das gleichzeitige Ausführen mehrerer Threads im selben Programm.
- GIL (Global Interpreter Lock): Dies ist die globale Interpreter-Sperre des Python-Interpreters, die einschränkt, dass nur ein Thread gleichzeitig Python-Bytecode ausführen kann. Daher kann Multithreading bei CPU-intensiven Aufgaben die Multi-Core-Prozessoren nicht voll ausnutzen.
2. Grundlagen des Threading-Moduls
Das threading
-Modul bietet Werkzeuge zum Erstellen und Verwalten von Threads. Hier sind einige häufig verwendete Klassen und Funktionen im threading
-Modul:
- Thread-Klasse: Eine Klasse, die zum Erstellen von Threads verwendet wird. Definieren Sie die Ausführungslogik des Threads, indem Sie die
Thread
-Klasse erben und dierun
-Methode implementieren. - start() Methode: Startet den Thread.
- join() Methode: Wartet, bis der Thread die Ausführung beendet hat.
- active_count() Funktion: Ruft die Anzahl der aktuell aktiven Threads ab.
3. Codepraxis: Multithreaded Image Downloading
Das Folgende demonstriert die Anwendung von Multithreading anhand eines Beispiels. Wir werden Multithreading verwenden, um eine Reihe von Bildern herunterzuladen.
import threading import requests from queue import Queue class LeapCellImageDownloader: def __init__(self, urls): self.urls = urls self.queue = Queue() def download_image(self, url): response = requests.get(url) if response.status_code == 200: filename = url.split("/")[-1] with open(filename, "wb") as f: f.write(response.content) print(f"Downloaded: {filename}") def worker(self): while True: url = self.queue.get() if url is None: break self.download_image(url) self.queue.task_done() def start_threads(self, num_threads=5): threads = [] for _ in range(num_threads): thread = threading.Thread(target=self.worker) thread.start() threads.append(thread) for url in self.urls: self.queue.put(url) self.queue.join() for _ in range(num_threads): self.queue.put(None) for thread in threads: thread.join() if __name__ == "__main__": image_urls = ["url1", "url2", "url3"] # Replace with the actual image URLs downloader = LeapCellImageDownloader(image_urls) downloader.start_threads()
In diesem Beispiel haben wir eine LeapCellImageDownloader
-Klasse erstellt, die eine worker
-Methode zum Herunterladen von Bildern enthält. Durch Multithreading können wir mehrere Bilder parallel herunterladen, was die Download-Effizienz verbessert.
4. Codeanalyse
- download_image-Methode: Verantwortlich für die spezifische Implementierung des Bilddownloads.
- worker-Methode: Als Ausführungslogik des Threads nimmt sie fortlaufend die herunterzuladende Bild-URL aus der Queue und ruft die Methode
download_image
auf. - start_threads-Methode: Startet eine angegebene Anzahl von Threads, legt die Bild-URLs in die Queue und wartet, bis alle Threads die Ausführung beendet haben.
6. Threadsicherheit und Lock-Mechanismus
In der Multithreading-Programmierung kann es zu einer Race Condition kommen, da mehrere Threads gleichzeitig auf gemeinsam genutzte Ressourcen zugreifen. Um diese Situation zu vermeiden, kann ein Lock-Mechanismus verwendet werden, um sicherzustellen, dass nur ein Thread zu einem bestimmten Zeitpunkt auf die gemeinsam genutzte Ressource zugreifen kann.
Das threading
-Modul stellt die Lock
-Klasse bereit. Dadurch kann ein Lock erstellt werden. Verwenden Sie die Methode acquire
, um das Lock zu erhalten, und die Methode release
, um das Lock freizugeben. Hier ist ein einfaches Beispiel:
import threading leapcell_counter = 0 leapcell_counter_lock = threading.Lock() def increment_counter(): global leapcell_counter for _ in range(1000000): with leapcell_counter_lock: leapcell_counter += 1 def main(): thread1 = threading.Thread(target=increment_counter) thread2 = threading.Thread(target=increment_counter) thread1.start() thread2.start() thread1.join() thread2.join() print("LeapCell Counter:", leapcell_counter) if __name__ == "__main__": main()
In diesem Beispiel haben wir eine globale Variable leapcell_counter
erstellt und ein Lock verwendet, um sicherzustellen, dass es keine Race Condition gibt, wenn die beiden Threads leapcell_counter
gleichzeitig ändern.
7. Anwendbare Szenarien für Multithreading
Multithreading eignet sich für die Bearbeitung von I/O-intensiven Aufgaben, wie z. B. Netzwerkanfragen, Lesen und Schreiben von Dateien usw. In diesen Szenarien kann der Thread die CPU freigeben, während er auf I/O wartet, wodurch andere Threads die Möglichkeit haben, ausgeführt zu werden, und die Gesamteffizienz des Programms verbessert wird.
Bei der Bearbeitung von CPU-intensiven Aufgaben kann Multithreading aufgrund des GIL von Python die Multi-Core-Prozessoren jedoch nicht voll ausnutzen, was zu Leistungsengpässen führen kann. Für CPU-intensive Aufgaben sollten Sie die Multiprocessing-Programmierung oder andere gleichzeitige Modelle in Betracht ziehen.
9. Ausnahmebehandlung und Multithreading
In der Multithreading-Programmierung kann die Ausnahmebehandlung komplexer werden. Da jeder Thread über seinen eigenen Ausführungskontext verfügt, kann eine Ausnahme in einem Thread ausgelöst, aber in einem anderen Thread abgefangen werden. Um Ausnahmen effektiv zu behandeln, müssen wir in jedem Thread geeignete Mechanismen zur Ausnahmebehandlung verwenden.
import threading def leapcell_thread_function(): try: # Some operations that may raise an exception result = 10 / 0 except ZeroDivisionError as e: print(f"Exception in LeapCell thread: {e}") if __name__ == "__main__": thread = threading.Thread(target=leapcell_thread_function) thread.start() thread.join() print("Main thread continues...")
In diesem Beispiel kann die Divisionsoperation im Thread leapcell_thread_function
eine ZeroDivisionError
-Ausnahme auslösen. Um diese Ausnahme abzufangen und zu behandeln, haben wir eine try-except
-Anweisung im Codeblock des Threads verwendet.
10. Vorsichtsmaßnahmen für Multithreading
Bei der Multithreading-Programmierung gibt es einige allgemeine Vorsichtsmaßnahmen, die besondere Aufmerksamkeit erfordern:
- Threadsicherheit: Stellen Sie sicher, dass keine Datenrennen und Inkonsistenzen auftreten, wenn mehrere Threads gleichzeitig auf gemeinsam genutzte Ressourcen zugreifen.
- Deadlock: Ein Deadlock kann auftreten, wenn mehrere Threads darauf warten, dass sich gegenseitig die Lock freigibt, und ein sorgfältiges Design und die Verwendung von Locks sind erforderlich.
- GIL-Einschränkungen: Die globale Interpreter-Sperre von Python kann die Leistungsverbesserung von Multithreading bei CPU-intensiven Aufgaben einschränken.
- Ausnahmebehandlung: Ausnahmen müssen in jedem Thread ordnungsgemäß behandelt werden, um zu verhindern, dass eine Ausnahme in einem Thread ausgelöst, aber nicht in anderen Threads abgefangen wird.
11. Leistungsoptimierung von Multithreading
In einigen Fällen können wir die Leistung von Multithreaded-Programmen durch einige Techniken optimieren:
- Threadpool: Verwenden Sie
ThreadPoolExecutor
im Modulconcurrent.futures
, um einen Threadpool zu erstellen und die Wiederverwendbarkeit von Threads zu verbessern. - Queue: Verwenden Sie eine Queue, um die Arbeit zwischen mehreren Threads zu koordinieren und das Producer-Consumer-Modell zu implementieren.
- Vermeiden Sie GIL-Einschränkungen: Für CPU-intensive Aufgaben sollten Sie die Verwendung anderer gleichzeitiger Modelle wie Multiprocessing und
asyncio
in Betracht ziehen.
13. Objektorientiertes Multithreading-Design
In der Praxis stehen wir in der Regel vor komplexeren Problemen und müssen Multithreading mit objektorientiertem Design kombinieren. Das Folgende ist ein einfaches Beispiel, das zeigt, wie man ein Multithreaded-Programm auf objektorientierte Weise entwirft:
import threading import time class LeapCellWorkerThread(threading.Thread): def __init__(self, name, delay): super().__init__() self.name = name self.delay = delay def run(self): print(f"{self.name} started.") time.sleep(self.delay) print(f"{self.name} completed.") if __name__ == "__main__": thread1 = LeapCellWorkerThread("LeapCell Thread 1", 2) thread2 = LeapCellWorkerThread("LeapCell Thread 2", 1) thread1.start() thread2.start() thread1.join() thread2.join() print("Main thread continues...")
In diesem Beispiel haben wir eine Klasse LeapCellWorkerThread
erstellt, die von der Klasse Thread
erbt und die Methode run
überschreibt, um die Ausführungslogik des Threads zu definieren. Jedem Thread wird ein Name und eine Verzögerungszeit zugewiesen.
14. Multithreading und Resource Manager
Betrachten Sie ein Szenario, in dem wir einen Resource Manager erstellen müssen, der für die Verwaltung der Zuweisung und Freigabe einer bestimmten Ressource verantwortlich ist. Zu diesem Zeitpunkt können wir Multithreading verwenden, um eine asynchrone Verwaltung von Ressourcen zu erreichen. Das Folgende ist ein Beispiel für einen einfachen Resource Manager:
import threading import time class LeapCellResourceManager: def __init__(self, total_resources): self.total_resources = total_resources self.available_resources = total_resources self.lock = threading.Lock() def allocate(self, request): with self.lock: if self.available_resources >= request: print(f"Allocated {request} LeapCell resources.") self.available_resources -= request else: print("Insufficient LeapCell resources.") def release(self, release): with self.lock: self.available_resources += release print(f"Released {release} LeapCell resources.") class LeapCellUserThread(threading.Thread): def __init__(self, name, resource_manager, request, release): super().__init__() self.name = name self.resource_manager = resource_manager self.request = request self.release = release def run(self): print(f"{self.name} started.") self.resource_manager.allocate(self.request) time.sleep(1) # Simulate some work with allocated resources self.resource_manager.release(self.release) print(f"{self.name} completed.") if __name__ == "__main__": manager = LeapCellResourceManager(total_resources=5) user1 = LeapCellUserThread("LeapCell User 1", manager, request=3, release=2) user2 = LeapCellUserThread("LeapCell User 2", manager, request=2, release=1) user1.start() user2.start() user1.join() user2.join() print("Main thread continues...")
In diesem Beispiel ist die Klasse LeapCellResourceManager
für die Verwaltung der Zuweisung und Freigabe von Ressourcen verantwortlich, und die Klasse LeapCellUserThread
stellt einen Benutzerthread dar, der Ressourcen verwendet. Durch die Verwendung eines Locks wird die sichere Zuweisung und Freigabe von Ressourcen gewährleistet.
16. Debugging und Performance Analysis von Multithreading
Bei der Multithreading-Programmierung sind Debugging und Performance Analysis wichtige Aspekte, die nicht ignoriert werden können. Python bietet einige Tools und Techniken, die uns helfen, Multithreaded-Programme besser zu verstehen und zu debuggen.
Debugging von Multithreaded-Programmen
- Verwendung von print-Anweisungen: Fügen Sie an geeigneten Stellen
print
-Anweisungen ein, um wichtige Informationen auszugeben, die helfen, den Ausführungsfluss des Programms zu verfolgen. - Logging-Modul: Verwenden Sie das
logging
-Modul von Python, um Informationen während der Programmlaufzeit aufzuzeichnen, einschließlich des Starts, des Endes und der wichtigsten Operationen von Threads. - pdb-Debugger: Fügen Sie Haltepunkte in den Code ein und verwenden Sie den eingebauten Debugger
pdb
von Python für interaktives Debugging.
import pdb # Insert a breakpoint in the code pdb.set_trace()
Performance Analysis von Multithreaded-Programmen
- Verwendung des timeit-Moduls: Durch die Einbettung von Timing-Code in den Code verwenden Sie das
timeit
-Modul, um die Ausführungszeit bestimmter Operationen oder Funktionen zu messen.
import timeit def my_function(): # The code to be tested # Test the execution time of the function execution_time = timeit.timeit(my_function, number=1) print(f"Execution time: {execution_time} seconds")
- Verwendung des cProfile-Moduls:
cProfile
ist das Performance Analysis-Tool von Python, mit dem Sie Funktionsaufrufe und Ausführungszeiten anzeigen können.
import cProfile def my_function(): # The code to be tested # Run the performance analysis cProfile.run("my_function()")
- Verwendung von Drittanbieter-Tools: Einige Drittanbieter-Tools, wie z. B.
line_profiler
,memory_profiler
usw., können detailliertere Performance Analysis-Informationen liefern, die helfen, Performance-Engpässe zu finden.
# Install line_profiler pip install line_profiler # Use line_profiler for performance analysis kernprof -l script.py python -m line_profiler script.py.lprof
17. Sicherheit und Risiken von Multithreading
Obwohl Multithreading-Programmierung die Programmleistung verbessern kann, bringt sie auch einige potenzielle Sicherheitsprobleme mit sich. Das Folgende sind einige Aspekte, die Aufmerksamkeit erfordern:
- Threadsicherheit: Stellen Sie sicher, dass der Zugriff auf gemeinsam genutzte Ressourcen threadsicher ist, was durch Lock-Mechanismen, atomare Operationen usw. gesteuert werden kann.
- Deadlock: Achten Sie bei der Verwendung von Locks auf das Auftreten von Deadlock, d. h. mehrere Threads warten aufeinander, um Ressourcen freizugeben, was dazu führt, dass das Programm nicht fortgesetzt werden kann.
- Ressourcenleck: In der Multithreading-Programmierung kommt es leicht zu der Situation, dass Ressourcen nicht ordnungsgemäß freigegeben werden, z. B. Threads, die nicht ordnungsgemäß geschlossen werden, oder Locks, die nicht ordnungsgemäß freigegeben werden.
- GIL-Einschränkungen: In CPU-intensiven Aufgaben kann die globale Interpreter-Sperre (GIL) zu einem Performance-Engpass werden, und eine sorgfältige Auswahl von Multithreading oder anderen gleichzeitigen Modellen ist erforderlich.
18. Erforschung anderer Concurrent Modelle
Obwohl Multithreading ein häufig verwendetes Concurrent Programmiermodell ist, ist es nicht die einzige Wahl. Python bietet auch einige andere Concurrent Modelle, einschließlich:
- Multiprocessing-Programmierung: Implementiert über das Modul
multiprocessing
. Jeder Prozess verfügt über einen unabhängigen Interpreter und GIL, der für CPU-intensive Aufgaben geeignet ist. - Asynchrone Programmierung: Implementiert über das Modul
asyncio
, basierend auf der Ereignisschleife und Coroutinen, geeignet für I/O-intensive Aufgaben, die die Concurrency des Programms verbessern können. - Parallele Berechnung: Verwenden Sie
ProcessPoolExecutor
undThreadPoolExecutor
im Modulconcurrent.futures
, um Aufgaben parallel auszuführen.
19. Kontinuierliches Lernen und Üben
Multithreading-Programmierung ist ein riesiges und komplexes Gebiet, und dieser Artikel bietet Ihnen nur eine einführende Anleitung. Kontinuierliches Lernen und Üben sind die Schlüssel, um die Multithreading-Programmierung eingehend zu beherrschen.
Es wird empfohlen, die offizielle Python-Dokumentation und verwandte Bücher zu lesen, um die verschiedenen Funktionen und die Verwendung des threading
-Moduls gründlich zu verstehen. Die Teilnahme an Open-Source-Projekten und das Lesen des Quellcodes anderer Leute sind ebenfalls gute Möglichkeiten, um Ihre Fähigkeiten zu verbessern.
21. Asynchronisierung von Multithreading und Coroutinen
In der modernen Programmierung sind asynchrone Programmierung und Coroutinen zu wichtigen Werkzeugen für die Handhabung von Hoch-Concurrency-Szenarien geworden. Python bietet das Modul asyncio
, um asynchrone Programmierung durch Coroutinen zu implementieren. Im Vergleich zum traditionellen Multithreading kann asynchrone Programmierung eine große Anzahl von I/O-intensiven Aufgaben effizienter verarbeiten, ohne eine große Anzahl von Threads zu erstellen.
Grundlagen der asynchronen Programmierung
Asynchrone Programmierung verwendet die Schlüsselwörter async
und await
, um Coroutinen zu definieren. Eine Coroutine ist ein schlanker Thread, der während der Laufzeit angehalten und fortgesetzt werden kann.
import asyncio async def leapcell_my_coroutine(): print("Start LeapCell coroutine") await asyncio.sleep(1) print("LeapCell Coroutine completed") async def leapcell_main(): await asyncio.gather(leapcell_my_coroutine(), leapcell_my_coroutine()) if __name__ == "__main__": asyncio.run(leapcell_main())
Im obigen Beispiel ist leapcell_my_coroutine
eine Coroutine, und asyncio.sleep
wird verwendet, um eine asynchrone Operation zu simulieren. Mehrere Coroutinen werden gleichzeitig über asyncio.gather
ausgeführt.
Vergleich zwischen asynchroner und Multithreading
- Performance: Asynchrone Programmierung kann eine große Anzahl von I/O-intensiven Aufgaben effizienter verarbeiten als Multithreading, da asynchrone Aufgaben die Steuerung abgeben können, wenn sie auf I/O warten, ohne die Ausführung anderer Aufgaben zu blockieren.
- Komplexität: Asynchrone Programmierung kann schwieriger zu schreiben und zu verstehen sein als Multithreading und erfordert Vertrautheit mit den Konzepten von Coroutinen und dem asynchronen Programmiermodell.
Beispiel: Asynchroner Image Download
Das Folgende ist ein einfaches Beispiel für die Verwendung von asynchroner Programmierung zur Implementierung des Image Downloads:
import asyncio import aiohttp async def leapcell_download_image(session, url): async with session.get(url) as response: if response.status == 200: filename = url.split("/")[-1] with open(filename, "wb") as f: f.write(await response.read()) print(f"LeapCell Downloaded: {filename}") async def leapcell_main(): image_urls = ["url1", "url2", "url3"] # Replace with the actual image URLs async with aiohttp.ClientSession() as session: tasks = [leapcell_download_image(session, url) for url in image_urls] await asyncio.gather(*tasks) if __name__ == "__main__": asyncio.run(leapcell_main())
In diesem Beispiel werden asynchrone HTTP-Anforderungen über die Bibliothek aiohttp
erstellt, und mehrere Coroutinen werden gleichzeitig über asyncio.gather
ausgeführt.
22. Ausnahmebehandlung in asynchroner Programmierung
In der asynchronen Programmierung unterscheidet sich auch die Art und Weise, wie Ausnahmen behandelt werden. In einer Coroutine verwenden wir normalerweise einen try-except
-Block oder Methoden wie asyncio.ensure_future
, um Ausnahmen zu behandeln.
import asyncio async def leapcell_my_coroutine(): try: # Asynchronous operation await asyncio.sleep(1) raise ValueError("An error occurred") except ValueError as e: print(f"LeapCell Caught an exception: {e}") async def leapcell_main(): task = asyncio.ensure_future(leapcell_my_coroutine()) await asyncio.gather(task) if __name__ == "__main__": asyncio.run(leapcell_main())
In diesem Beispiel umschließt asyncio.ensure_future
die Coroutine in ein Task
-Objekt. Durch die Verwendung von await asyncio.gather
, um auf den Abschluss der Aufgabe zu warten, wird die Ausnahme abgefangen.
23. Vorteile und Vorsichtsmaßnahmen der asynchronen Programmierung
Vorteile
- Hohe Concurrency: Asynchrone Programmierung eignet sich für eine große Anzahl von I/O-intensiven Aufgaben. Sie kann Concurrent Anforderungen effizienter verarbeiten und den Durchsatz des Systems verbessern.
- Ressourceneffizienz: Im Vergleich zu Multithreading spart asynchrone Programmierung in der Regel mehr Ressourcen, da Coroutinen schlank sind und mehrere Coroutinen in einem einzelnen Thread ausgeführt werden können.
Vorsichtsmaßnahmen
- Blockierende Operationen: In der asynchronen Programmierung wirken sich blockierende Operationen auf die gesamte Ereignisschleife aus, und blockierende Aufrufe sollten so weit wie möglich vermieden werden.
- Ausnahmebehandlung: Die Ausnahmebehandlung in der asynchronen Programmierung kann komplexer sein, und die Ausnahmesituationen in Coroutinen müssen sorgfältig behandelt werden.
- Anwendbare Szenarien: Asynchrone Programmierung eignet sich eher für I/O-intensive Aufgaben als für CPU-intensive Aufgaben.
24. Erforschung weiterer asynchroner Programmierwerkzeuge und -bibliotheken
Zusätzlich zu asyncio
und aiohttp
gibt es einige andere leistungsstarke asynchrone Programmierwerkzeuge und -bibliotheken:
- asyncpg: Ein asynchroner PostgreSQL-Datenbanktreiber.
- aiofiles: Eine asynchrone Dateioperationsbibliothek.
- aiohttp: Ein asynchrones HTTP-Client- und Serverframework.
- aiomysql: Ein asynchroner MySQL-Datenbanktreiber.
- uvloop: Eine Hochleistungs-Ereignisschleife, die zum Ersetzen der Standard-Ereignisschleife verwendet wird.
25. Kontinuierliches Lernen und Üben
Asynchrone Programmierung ist ein breites und tiefgründiges Thema, und dieser Artikel bietet Ihnen nur eine kurze Einführung. Es wird empfohlen, die Dokumentation des Moduls asyncio
eingehend zu studieren, um Konzepte wie die Ereignisschleife, Coroutinen und asynchrone Operationen zu verstehen.
Gleichzeitig werden Sie durch praktische Projekte die Techniken und Best Practices der asynchronen Programmierung besser verstehen und beherrschen.
Fazit
Dieser Artikel hat die Multithreading-Programmierung und die asynchrone Programmierung in Python eingehend untersucht und das grundlegende Wissen des Multithreading-Moduls (threading
), die Codepraxis sowie die grundlegenden Konzepte und die Verwendung des asynchronen Programmiermoduls (asyncio
) behandelt. Wir begannen mit den Grundlagen des Multithreading, wie z. B. der Thread
-Klasse, dem Lock-Mechanismus, der Threadsicherheit usw., und demonstrierten schrittweise die Anwendungsszenarien und Vorsichtsmaßnahmen des Multithreading in praktischen Anwendungen. Anhand eines Beispiels wurde der Prozess des Multithreaded-Image-Downloads gezeigt, wobei die Bedeutung von Threadsicherheit und Ausnahmebehandlung hervorgehoben wurde.
Leapcell: Das Beste aus Serverless Web Hosting
Schließlich möchte ich eine Plattform empfehlen, die sich am besten für die Bereitstellung von Python-Diensten eignet: Leapcell
🚀 Build with Your Favorite Language
Develop effortlessly in JavaScript, Python, Go, or Rust.
🌍 Deploy Unlimited Projects for Free
Only pay for what you use—no requests, no charges.
⚡ Pay-as-You-Go, No Hidden Costs
No idle fees, just seamless scalability.
🔹 Follow us on Twitter: @LeapcellHQ