Heim > Backend-Entwicklung > Python-Tutorial > Einführung in BlockBuster: Ist meine Asyncio-Ereignisschleife blockiert?

Einführung in BlockBuster: Ist meine Asyncio-Ereignisschleife blockiert?

Patricia Arquette
Freigeben: 2025-01-09 06:29:43
Original
919 Leute haben es durchsucht

Introducing BlockBuster: is my asyncio event loop blocked?

Python 3.5 führte asynchrone E/A als Alternative zu Threads zur Handhabung der Parallelität ein. Der Vorteil der asynchronen E/A und der Asyncio-Implementierung in Python besteht darin, dass das System weniger Ressourcen verbraucht und skalierbarer ist, da keine speicherintensiven Betriebssystem-Threads erzeugt werden. Darüber hinaus werden bei Asyncio Planungspunkte klar über die await-Syntax definiert, während bei Thread-basierter Parallelität die GIL an unvorhersehbaren Codepunkten freigegeben werden kann. Dadurch sind Asyncio-basierte Parallelitätssysteme einfacher zu verstehen und zu debuggen. Schließlich kann die Asyncio-Aufgabe abgebrochen werden, was bei der Verwendung von Threads nicht einfach ist.

Um diese Vorteile wirklich nutzen zu können, ist es jedoch wichtig, das Blockieren von Aufrufen in asynchronen Coroutinen zu vermeiden. Blockierende Aufrufe können Netzwerkaufrufe, Dateisystemaufrufe, sleep-Aufrufe usw. sein. Diese blockierenden Aufrufe sind schädlich, da Asyncio unter der Haube eine Single-Thread-Ereignisschleife verwendet, um Coroutinen gleichzeitig auszuführen. Wenn Sie also einen blockierenden Aufruf in einer Coroutine durchführen, blockiert dieser die gesamte Ereignisschleife und alle Coroutinen, was sich auf die Gesamtleistung Ihrer Anwendung auswirkt.

Das Folgende ist ein Beispiel für einen Blockierungsaufruf, der verhindert, dass Code gleichzeitig ausgeführt wird:

import asyncio
import datetime
import time

async def example(name):
    print(f"{datetime.datetime.now()}: {name} start")
    time.sleep(1)  # time.sleep 是一个阻塞函数
    print(f"{datetime.datetime.now()}: {name} stop")

async def main():
    await asyncio.gather(example("1"), example("2"))

asyncio.run(main())
Nach dem Login kopieren
Nach dem Login kopieren

Das Laufergebnis ist ähnlich wie:

<code>2025-01-07 18:50:15.327677: 1 start
2025-01-07 18:50:16.328330: 1 stop
2025-01-07 18:50:16.328404: 2 start
2025-01-07 18:50:17.333159: 2 stop</code>
Nach dem Login kopieren
Nach dem Login kopieren

Wie Sie sehen können, laufen die beiden Coroutinen nicht gleichzeitig.

Um dieses Problem zu lösen, müssen Sie ein nicht blockierendes Äquivalent verwenden oder die Ausführung auf den Thread-Pool verschieben:

import asyncio
import datetime
import time

async def example(name):
    print(f"{datetime.datetime.now()}: {name} start")
    await asyncio.sleep(1)  # 将阻塞的 time.sleep 调用替换为非阻塞的 asyncio.sleep 协程
    print(f"{datetime.datetime.now()}: {name} stop")

async def main():
    await asyncio.gather(example("1"), example("2"))

asyncio.run(main())
Nach dem Login kopieren
Nach dem Login kopieren

Das Laufergebnis ist ähnlich wie:

<code>2025-01-07 18:53:53.579738: 1 start
2025-01-07 18:53:53.579797: 2 start
2025-01-07 18:53:54.580463: 1 stop
2025-01-07 18:53:54.580572: 2 stop</code>
Nach dem Login kopieren

Hier laufen zwei Coroutinen gleichzeitig.

Das Problem besteht nun darin, dass es nicht immer einfach ist zu erkennen, ob eine Methode blockiert oder nicht. Vor allem, wenn die Codebasis groß ist oder Bibliotheken von Drittanbietern verwendet. Manchmal werden blockierende Aufrufe in tiefen Teilen des Codes durchgeführt.

Blockiert dieser Code beispielsweise?

import blockbuster
from importlib.metadata import version

async def get_version():
    return version("blockbuster")
Nach dem Login kopieren

Lädt Python beim Start Paketmetadaten in den Speicher? Ist dies erledigt, wenn das Modul blockbuster geladen wird? Oder wenn wir version() anrufen? Werden die Ergebnisse zwischengespeichert und sind nachfolgende Aufrufe nicht blockierend? Die richtige Antwort erfolgt beim Aufruf von version(), was das Lesen der METADATA-Datei des installierten Pakets beinhaltet. Und die Ergebnisse werden nicht zwischengespeichert. Daher ist version() ein blockierender Aufruf und sollte immer an den Thread verschoben werden. Es ist schwer, diese Tatsache zu erkennen, ohne sich mit dem Code von importlib auseinanderzusetzen.

Eine Möglichkeit, blockierende Aufrufe zu erkennen, besteht darin, den Debug-Modus von Asyncio zu aktivieren, um blockierende Aufrufe zu protokollieren, die zu lange dauern. Dies ist jedoch nicht der effizienteste Ansatz, da viele Blockierungszeiten, die kürzer als das Trigger-Timeout sind, immer noch die Leistung beeinträchtigen und die Blockierungszeiten im Test/in der Entwicklung möglicherweise anders sind als in der Produktion. Beispielsweise können Datenbankaufrufe in einer Produktionsumgebung länger dauern, wenn die Datenbank große Datenmengen abrufen muss.

Hier kommt BlockBuster ins Spiel! Bei Aktivierung patcht BlockBuster mehrere blockierende Python-Framework-Methoden, die Fehler auslösen, wenn sie aus der Asyncio-Ereignisschleife aufgerufen werden. Zu den Standard-Patching-Methoden gehören Methoden der Module os, io, time, socket und sqlite. Eine vollständige Liste der von BlockBuster erkannten Methoden finden Sie in der Projekt-Readme-Datei. Anschließend können Sie BlockBuster im Unit-Test- oder Entwicklungsmodus aktivieren, um blockierende Aufrufe abzufangen und zu beheben. Wenn Sie die großartige BlockHound-Bibliothek für die JVM kennen, ist es das gleiche Prinzip, aber für Python. BlockHound war dank der Macher eine großartige Inspirationsquelle für BlockBuster.

Sehen wir uns an, wie Sie BlockBuster für das obige Blockierungscode-Snippet verwenden.

Zuerst müssen wir das blockbuster Paket

installieren
import asyncio
import datetime
import time

async def example(name):
    print(f"{datetime.datetime.now()}: {name} start")
    time.sleep(1)  # time.sleep 是一个阻塞函数
    print(f"{datetime.datetime.now()}: {name} stop")

async def main():
    await asyncio.gather(example("1"), example("2"))

asyncio.run(main())
Nach dem Login kopieren
Nach dem Login kopieren

Wir können dann das Pytest-Fixture und die blockbuster_ctx()-Methode verwenden, um den BlockBuster zu Beginn jedes Tests zu aktivieren und ihn während des Teardowns zu deaktivieren.

<code>2025-01-07 18:50:15.327677: 1 start
2025-01-07 18:50:16.328330: 1 stop
2025-01-07 18:50:16.328404: 2 start
2025-01-07 18:50:17.333159: 2 stop</code>
Nach dem Login kopieren
Nach dem Login kopieren

Wenn Sie dies mit Pytest ausführen, erhalten Sie

import asyncio
import datetime
import time

async def example(name):
    print(f"{datetime.datetime.now()}: {name} start")
    await asyncio.sleep(1)  # 将阻塞的 time.sleep 调用替换为非阻塞的 asyncio.sleep 协程
    print(f"{datetime.datetime.now()}: {name} stop")

async def main():
    await asyncio.gather(example("1"), example("2"))

asyncio.run(main())
Nach dem Login kopieren
Nach dem Login kopieren

Hinweis: Typischerweise wird in einem realen Projekt das blockbuster()-Gerät in einer conftest.py-Datei eingerichtet.

Fazit

Ich glaube, dass BlockBuster in Asyncio-Projekten sehr nützlich ist. Es hat mir geholfen, viele Probleme mit blockierenden Anrufen in Projekten, an denen ich gearbeitet habe, zu erkennen. Aber es ist kein Allheilmittel. Insbesondere verwenden einige Bibliotheken von Drittanbietern keine Python-Framework-Methoden für die Interaktion mit dem Netzwerk oder Dateisystem, sondern umschließen stattdessen C-Bibliotheken. Für diese Bibliotheken können Sie in Ihrem Test-Setup Regeln hinzufügen, um blockierende Aufrufe an diese Bibliotheken auszulösen. BlockBuster ist ebenfalls Open Source: Beiträge sind herzlich willkommen, um Regeln für Ihre Lieblingsbibliotheken im Kernprojekt hinzuzufügen. Wenn Sie Probleme und Verbesserungsmöglichkeiten sehen, würde ich mich über Ihr Feedback im Projekt-Issue-Tracker freuen.

Einige Links:

  • GitHub-Projekt
  • Frage
  • Pakete auf Pypi

Das obige ist der detaillierte Inhalt vonEinführung in BlockBuster: Ist meine Asyncio-Ereignisschleife blockiert?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Erklärung dieser Website
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
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage