Asynchrone Python-Programmierung: Ein kurzer Überblick

Wenn es um die Programmausführung geht, bedeutet „asynchrone Ausführung“ eine Situation, in der das Programm nicht auf den Abschluss eines bestimmten Prozesses wartet, sondern unabhängig davon weiterarbeitet. Ein Beispiel für asynchrone Programmierung ist ein Dienstprogramm, das asynchron arbeitet und in eine Protokolldatei schreibt. Obwohl ein solches Dienstprogramm möglicherweise fehlschlägt (z. B. weil nicht genügend Speicherplatz vorhanden ist), funktioniert es in den meisten Fällen ordnungsgemäß und kann in verschiedenen Programmen verwendet werden. Sie können sie anrufen, die Daten für die Aufzeichnung weitergeben und danach ihre eigene Arbeit fortsetzen.



Die Verwendung asynchroner Mechanismen beim Schreiben eines bestimmten Programms bedeutet, dass dieses Programm schneller ausgeführt wird als ohne Verwendung solcher Mechanismen. Gleichzeitig sollte das, was asynchron gestartet werden soll, wie z. B. ein Dienstprogramm für die Protokollierung, unter Berücksichtigung von Notfällen geschrieben werden. Beispielsweise kann ein Dienstprogramm zur Protokollierung, wenn der Speicherplatz knapp wird, die Protokollierung einfach stoppen und das Hauptprogramm nicht mit einem Fehler "abstürzen".

Bei der asynchronen Codeausführung wird dieser Code normalerweise in einem separaten Thread ausgeführt. Dies ist - wenn wir über ein System mit einem Single-Core-Prozessor sprechen. Auf Systemen mit Mehrkernprozessoren kann ein solcher Code durchaus von einem Prozess ausgeführt werden, der einen separaten Kern verwendet. Ein Single-Core-Prozessor kann zu einem bestimmten Zeitpunkt nur einen Befehl lesen und ausführen. Es ist wie Bücher zu lesen. Sie können nicht zwei Bücher gleichzeitig lesen.

Wenn Sie ein Buch lesen und jemand anderes Ihnen ein anderes Buch gibt, können Sie dieses zweite Buch nehmen und anfangen, es zu lesen. Aber der erste muss verschoben werden. Die Ausführung von Code mit mehreren Threads erfolgt nach demselben Prinzip. Und wenn mehrere Ihrer Exemplare mehrere Bücher gleichzeitig lesen würden, würde dies der Funktionsweise von Multiprozessorsystemen ähneln.

Wenn es auf einem Single-Core-Prozessor sehr schnell ist, zwischen Aufgaben zu wechseln, die unterschiedliche Rechenleistung erfordern (z. B. zwischen bestimmten Berechnungen und dem Lesen von Daten von einer Festplatte), hat man möglicherweise das Gefühl, dass ein einzelner Prozessorkern mehrere Aufgaben gleichzeitig ausführt. Dies passiert beispielsweise, wenn Sie versuchen, mehrere Websites gleichzeitig in einem Browser zu öffnen. Wenn der Browser zum Laden jeder Seite einen separaten Stream verwendet, erfolgt alles viel schneller als wenn diese Seiten einzeln geladen würden. Das Laden einer Seite ist keine so schwierige Aufgabe, dass die Ressourcen des Systems nicht voll ausgeschöpft werden. Infolgedessen ist das gleichzeitige Starten mehrerer solcher Aufgaben ein sehr effektiver Schritt.

Asynchrone Python-Programmierung


Ursprünglich verwendete Python Generator-basierte Coroutinen, um asynchrone Programmieraufgaben zu lösen. Dann erschien in Python 3.4 das asyncio Modul (manchmal wird sein Name als asynchrone asyncio geschrieben), das asynchrone Programmiermechanismen implementiert. In Python 3.5 wurde das async / await-Konstrukt eingeführt.

Um eine asynchrone Entwicklung in Python durchführen zu können, müssen Sie sich mit einigen Konzepten befassen. Das sind Coroutine und Aufgabe.

Coroutinen


Normalerweise ist Coroutine eine asynchrone Funktion. Coroutine kann auch ein Objekt sein, das von einer Coroutine-Funktion zurückgegeben wird.

Wenn beim Deklarieren einer Funktion angegeben wird, dass sie asynchron ist, können Sie sie mit dem Schlüsselwort await aufrufen:

 await say_after(1, 'hello') 

Solch eine Konstruktion bedeutet, dass das Programm ausgeführt wird, bis es auf einen Warte-Ausdruck stößt, wonach es die Funktion aufruft und ihre Ausführung anhält, bis die Arbeit der aufgerufenen Funktion abgeschlossen ist. Danach können auch andere Coroutinen starten.

Das Anhalten eines Programms bedeutet, dass die Steuerung zur Ereignisschleife zurückkehrt. Bei Verwendung des asyncio Moduls führt die Ereignisschleife alle asynchronen Aufgaben, E / A- asyncio und Unterprozesse aus. In den meisten Fällen werden Tasks zum Ausführen von corutin verwendet.

Die Aufgaben


Mit Aufgaben können Sie Coroutinen in einer Ereignisschleife ausführen. Dies vereinfacht die Ausführungskontrolle mehrerer Coroutinen. Hier ist ein Beispiel, das Koroutinen und Aufgaben verwendet. Beachten Sie, dass Entitäten, die mit dem Konstrukt async def deklariert wurden, Coroutinen sind. Dieses Beispiel stammt aus der offiziellen Python- Dokumentation .

 import asyncio import time async def say_after(delay, what):    await asyncio.sleep(delay)    print(what) async def main():    task1 = asyncio.create_task(        say_after(1, 'hello'))    task2 = asyncio.create_task(        say_after(2, 'world'))    print(f"started at {time.strftime('%X')}")    #     (      #  2 .)    await task1    await task2    print(f"finished at {time.strftime('%X')}") asyncio.run(main()) 

Die Funktion say_after() hat das async Präfix, daher haben wir eine Coroutine. Wenn wir ein wenig von diesem Beispiel abweichen, können wir sagen, dass diese Funktion folgendermaßen aufgerufen werden kann:

     await say_after(1, 'hello')    await say_after(2, 'world') 

Bei diesem Ansatz werden die Coroutinen jedoch nacheinander aufgerufen und es dauert ungefähr 3 Sekunden, bis sie abgeschlossen sind. In unserem Beispiel werden sie wettbewerbsfähig eingeführt. Für jeden von ihnen wird eine Aufgabe verwendet. Dadurch beträgt die Ausführungszeit des gesamten Programms ca. 2 Sekunden. Damit ein solches Programm funktioniert, reicht es nicht aus, die main() Funktion mit dem async . In solchen Situationen müssen Sie das asyncio Modul verwenden.

Wenn Sie den Beispielcode ausführen, wird auf dem Bildschirm ein Text ähnlich dem folgenden angezeigt:

 started at 20:19:39 hello world finished at 20:19:41 

Beachten Sie, dass sich die Zeitstempel in der ersten und letzten Zeile um 2 Sekunden unterscheiden. Wenn Sie dieses Beispiel mit einem sequentiellen Aufruf von corutin ausführen, beträgt die Differenz zwischen den Zeitstempeln 3 Sekunden.

Beispiel


In diesem Beispiel wird die Anzahl der Operationen bestimmt, die erforderlich sind, um die Summe von zehn Elementen einer Folge von Zahlen zu berechnen. Die Berechnungen beginnen am Ende der Sequenz. Eine rekursive Funktion beginnt mit der Nummer 10 und ruft sich dann selbst mit den Nummern 9 und 8 auf. Dabei wird addiert, was zurückgegeben wird. Dies wird fortgesetzt, bis die Berechnungen abgeschlossen sind. Als Ergebnis stellt sich beispielsweise heraus, dass die Summe einer Folge von Zahlen von 1 bis 10 55 ist. Gleichzeitig ist unsere Funktion sehr ineffizient, hier wird die time.sleep(0.1) verwendet.

Hier ist der Funktionscode:

 import time def fib(n):    global count    count=count+1    time.sleep(0.1)    if n > 1:        return fib(n-1) + fib(n-2)    return n start=time.time() global count count = 0 result = fib(10) print(result,count) print(time.time()-start) 

Was passiert, wenn Sie diesen Code mithilfe asynchroner Mechanismen umschreiben und asyncio.gather das Konstrukt asyncio.gather anwenden, das für die Ausführung von zwei Aufgaben verantwortlich ist und auf deren Abschluss wartet?

 import asyncio,time async def fib(n):    global count    count=count+1    time.sleep(0.1)    event_loop = asyncio.get_event_loop()    if n > 1:        task1 = asyncio.create_task(fib(n-1))        task2 = asyncio.create_task(fib(n-2))        await asyncio.gather(task1,task2)        return task1.result()+task2.result()    return n 

Tatsächlich arbeitet dieses Beispiel sogar ein wenig langsamer als das vorherige, da alles in einem Thread ausgeführt wird und Aufrufe von create_task , gather und anderen auf diese Weise eine zusätzliche Belastung des Systems verursachen. Der Zweck dieses Beispiels besteht jedoch darin, die Fähigkeit zu demonstrieren, an mehreren Aufgaben teilzunehmen und auf deren Abschluss zu warten.

Zusammenfassung


Es gibt Situationen, in denen die Verwendung von Tasks und Corutin sehr nützlich ist: Wenn ein Programm beispielsweise eine Mischung aus Eingabe-Ausgabe und Berechnungen enthält oder wenn verschiedene Berechnungen in demselben Programm ausgeführt werden, können Sie diese Probleme lösen, indem Sie Code in einem Konkurrenzprogramm ausführen im sequentiellen Modus. Dies hilft, die Zeit zu verkürzen, die das Programm benötigt, um bestimmte Aktionen auszuführen. Dies erlaubt jedoch beispielsweise nicht die gleichzeitige Ausführung von Berechnungen. Multiprocessing wird verwendet, um solche Berechnungen zu organisieren. Dies ist ein eigenständiges großes Thema.

Sehr geehrte Leser! Wie schreibt man asynchronen Python-Code?


Source: https://habr.com/ru/post/de475246/


All Articles