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')}")
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?
