Ich lese mich gerade ein bisschen ein in die asyncio-Programmierung mit Python. Was ich nicht verstehe: ich dachte eigentlich dass Linux "preemptive multitasking" macht. Das bedeutet doch eigentlich: macht ein Programm eine Aktion, die blockt, wie z.B. I/O, Sleep, Wait, Socket-IO, dann bekommt es vom Scheduler die CPU weggenommen und das nächste Programm bekommt die CPU. Wenn also ein Python-Programm time.sleep() macht, dann wird doch wohl kaum die CPU wirklich damit beschäftigt sein, die Sekunden für den Sleep zu zählen. Oder wenn auf die Antwort eines http-Requests gewartet wird, wird doch die CPU keinen idle-Loop machen und permanent das Ergebnis pollen. Welchen Vorteil hat dann die asyncio-Technik, wenn das Betriebssystem das schon von sich aus macht?
Python und asyncio
![]() Anmeldungsdatum: Beiträge: 870 Wohnort: Schwetzingen |
|
Projektleitung
Anmeldungsdatum: Beiträge: 13174 |
Das normale (und für die Programmiererin am leichtesten zu verstehende) Verfahren ist ja blockierender IO. Wenn man das nutzt, ist der Thread, der z.B. den sleep aufruft oder von einem Dateideskriptor liest, geblockt. Das ist erst einmal nicht schlimm, aber der Overhead (im Sinne von Speicherverbrauch) eines Threads ist schon bedeutsam (jeder Thread braucht einen Callstack), wenn man viele IO-Operationen gleichzeitig hat (wie z.B. bei einem Webserver mit viel Traffic). Ich kenne jetzt Hier steht eine Erklärung - allerdings auf Englisch. Das Beispiel mit Judith erklärt es eigentlich recht gut. |
(Themenstarter)
![]() Anmeldungsdatum: Beiträge: 870 Wohnort: Schwetzingen |
Meinst du wirklich der Overhead von Threads ist so groß? Ich beschäftige mit viel mit home assistant und diese async-Programmierung ist sehr verwirrend. Auf der anderen Seite scheint es tatsächlich keine Threads zu geben, es ist nur ein Programmfaden. Aber irgendwo muss der Zustand eines unterbrochenen Aufrufs doch auch gespeichert werden? |
![]() Anmeldungsdatum: Beiträge: 2133 Wohnort: Gelsenkirchen |
Es geht darum, dass der Aufruf in Python blockiert. Was das Betriebssystem intern damit macht, ist an der Stelle nicht relevant. Der Programmfluss bleibt halt stehen. Mit asynchroner Programmierung, Multiprocessing, Threads und ähnlichen Kniffen umgeht man das Problem. Zu beachten ist, dass Python-Threads aus technischen Gründen nicht in der Lage sind, mehrere Kerne zu nutzen. Insofern wird das nur für I/O-lastige Aufgaben (z.B. im Netzwerk) empfohlen. Aufwändige Berechnungen werden daher mit Python-Threads nicht schneller, da effektiv eben nur ein Kern genutzt wird. Eher sogar etwas langsamer aufgrund des Verwaltungsaufwands für die Threads. Nebenläufige Programmierung - egal auf welcher Grundlage - ist immer etwas komplizierter. Je nach Aufgabengebiet gibt es diverse externe Bibliotheken, die du für Python nutzen kannst. Mit deren API ist es meist nicht mehr ganz so umständlich in der Umsetzung. |
![]() Anmeldungsdatum: Beiträge: 17604 Wohnort: Berlin |
Richtig ist, dass andere Programme weiterlaufen können, während ein Programm x auf die Antwort zu einem Webrequest wartet oder ein Sleep durchführt. Aber das Programm x selbst ist so lange blockiert, außer eben man trifft Vorkehrungen, um Teile des Programms, die parallel laufen können, auszuführen. Diese asyncio-Geschichten sind wahrscheinlich genau so eine Vorkehrung. Trivial ist das ja oft nicht, weil das Programm ja mit Ergebnissen der Abfrage oft etwas anfangen will. Vielleicht muss das Programm ja 10 Adressen abfragen und kann die Abfrage 2..10 schon starten, während Thread 1 schon wartet. |
![]() Anmeldungsdatum: Beiträge: 2133 Wohnort: Gelsenkirchen |
Hier übrigens ein Beispiel für asynchrone HTTP-Abfragen: https://www.twilio.com/blog/asynchronous-http-requests-in-python-with-aiohttp Meistens reicht sowas schon aus, um gängige Probleme zu lösen. Für einen tieferen Einstieg ist natürlich mehr Einarbeitung notwendig. Da kenne ich mich dann aber auch nicht mehr im Detail aus... |
Anmeldungsdatum: Beiträge: 29567 |
Hallo,
Das ist im Kern dein Denkfehler, weil:
Das Betriebssystem verwaltet _alle_ laufenden Programme. Aber nicht desto trotz kann jedes Programm X Prozess oder Threads haben. Bei Go kannst du z.B. Goroutines nutzen, um X Sachen quasi gleichzeitig zu machen. Die Standardimplementierung von Python, CPython, hat den GIL 🇬🇧, d.h. CPython kann per se nur einen Thread gleichzeitig handhaben, völlig unabhängig vom darunter liegenden Betriebssystem. Ein IMHO sehr interessanter (wenn auch älteres) Video dazu gibt es von Dave Beazly 🇬🇧. Es gibt übrigens auch andere Python-Implementierungen, die keinen GIL haben. Bei asyncio läuft genau ein Thread, der Event Loop, der aber X Aufgaben verwaltet, die Tasks bzw. Coroutines. Macht wie schon gesagt wurde nur bei I/O lastigen Sachen (wie Netzwerksachen) Sinn, nicht bei CPU-lastigen Sachen. Nebenläufige Programmierung ist generell nicht einfach zu verstehen, weil man sich gedanklich vom linearen Programmablauf verabschieden muss. Bei Event-Loops ist es noch ein Stufe schwieriger, weil am Ende der Event-Loop entscheidet, wann was wie warum dran ist. Im Kern ist asyncio ja auch entstanden, um im ersten Linie in der Implementierung von Python Netzwerk I/O effizient abbilden zu können, ohne auf externe Bibliotheken wie Twisted nutzen zu müssen. Wenn du dich mit asyncio beschäftigen willst, dann lohnt sich IMHO auch ein Blick auf Trio 🇬🇧. Sehr gute Doku und IMHO eine freundlichere API als asyncio in Python. Gruß, noisefloor Gruß, noisefloor |
(Themenstarter)
![]() Anmeldungsdatum: Beiträge: 870 Wohnort: Schwetzingen |
Ok dann frag ich mal andersrum: aus OS-Sicht gibt es einige böse Dinge, die man vermeiden muss:
Overhead durch zu viel I/O lasse ich mal weg, denn wenn I/O gemacht werden muss kommt man nicht drum herum. Welcher dieser 3 Punkte wird durch asynchrones Programmieren verhindert? Oder falls es keiner dieser 3 ist: welches Problem wird durch asynchrones Programmieren gelöst? |
Anmeldungsdatum: Beiträge: 29567 |
Hallo, die Frage macht so keinen Sinn. Entweder hast du ein Problem, was (nur) durch nebenläufige Programmierung gelöst werden kann - dann nutzt du nebenläufige Programmierung. Und wenn nicht, dann nicht. Und das Betriebssystem hat damit immer noch nichts zu tun. Wenn überhaupt die Hardware. CPU Zeit und Speicher kannst du auch auch mit einem linear laufenden Programm verbraten, dafür ist keine nebenläufige Programmierung notwendig. Bei Python bzw. CPython ist je nach Problemstellung eher die Frage, ob du Prozess-basierte Nebenläufigkeit nutzt (Multiprocessing, mehrere Prozesse können auf Mehrkern CPU auch wirklich parallel laufen) oder kooperatives Multitasking (asyncio, Threads).
asyncio: Viele Netzwerkverbindungen in einem Thread / Event-Loop verwalten. Man kann das auch anders sagen: wenn man nicht weiß, welches Problem asyncio für einen selber lösen könnte, dann braucht man auch kein asyncio. Gruß, noisefloor |
(Themenstarter)
![]() Anmeldungsdatum: Beiträge: 870 Wohnort: Schwetzingen |
Dann stellt sich immer noch die Frage, welches Problem asyncio bei home assistant löst? Sind denn alle Webserver asynchron programmiert? |
Anmeldungsdatum: Beiträge: 29567 |
Hallo,
Welcher Home Assistant genau? Wenn der Code Open Source ist kannst du ja den Quellcode durchlesen. Oder die Entwickler fragen. Was ich mir verstellen könnte: beim Home Assistant geht es im Kerne wahrscheinlich um die Kommunikation mit X Geräten, unterm Strich also in irgendeiner Form Netzwerk-IO. Falls du selber Code für ein Gerät schreiben oder was ist den Hintergrund der Frage?
Nein. Die gibt es in allen Formen und Farben, von single-thread kann Requests nur sequentiell abarbeiten über nebenläufig via Thread- oder Prozessworker bis hin zu asynchron in einem Event-Loop. Gruß, noisefloor |
Projektleitung
Anmeldungsdatum: Beiträge: 13174 |
Da muss man etwas genauer sein: das Programmiermodell von Threads ist nicht kooperativ, weil der Programmierer die Maßnahmen zur Abgabe der Kontrolle - anders als bei asyncio - nicht sieht bzw. nutzen muss. Man könnte höchstens Green Threads als kooperativ bezeichnen, aber das bezieht sich dann auf deren Implementierung. |
(Themenstarter)
![]() Anmeldungsdatum: Beiträge: 870 Wohnort: Schwetzingen |
Ich komme eher vom Mainframe und da ist es so dass einem Programm bei jeder Aktion die nicht im Programm geschieht erst einmal die Kontrolle entzogen wird. Also bei jeder I/O usw. wird die CPU vom Scheduler neu zugeteilt. Kooperation wäre also nur dann nötig, wenn ein Programm numerische Berechnungen macht und dazu minutenlang reinen Programmcode ausführt. In dem Fall bekommt ein Programm die CPU gewaltsam weggenommen, je nach Anwendungspriorität. |
Anmeldungsdatum: Beiträge: 29567 |
Hallo, @jms3000: nochmal im Klartext - solange du nicht vergisst, was auf Hardware- und das Betriebssystemeben passiert, wenn du dich mit asyncio, Threads und Multiprocessing unter Python beschäftigst, dann stehst du dir krass selber im Weg. Relevant ist in 1. Instanz alleine, wie der Python-Interpreter das umsetzt.
Was ich sagen wollte: unter CPython laufen aus Python heraus gestartet Threads, also via Gruß, noisefloor |
Ehemalige
![]() Anmeldungsdatum: Beiträge: 4673 Wohnort: Berlin |
Das mit dem GIL ist mir etwas zu düster ausgedrückt. Ja es gibt das GIL, aber das erzwingt nur das Python-Bytecode immer nur in einem Thread zur gleichen Zeit abgearbeitet werden kann. Sobald etwas natives, zum Beispiel in C geschriebenes ausgeführt wird, kann das GIL von dem entsprechenden Thread freigegeben werden und es wird Code parallel ausgeführt. Das passiert zum einen bei den ganzen blockierenden Systemaufrufen, aber auch Bibliotheken wie Numpy geben das GIL frei wenn in C, C++, Fortran, … geschriebener Code mit den Arrays rechnet. Wenn das CPU-lastige nur in reinem Python, also in Python-Bytecode passiert, dann bringen Threads nichts, das stimmt. Aber wenn man Sachen macht bei denen das GIL freigegeben wird, weil Berechnungen von externen, ”nativen” Bibliotheken durchgeführt werden, dann können Threads durchaus ein bisschen was an Performance bringen wenn Teile davon parallel gemacht werden können. Und es gibt gerade mal wieder Bestrebungen das GIL in CPython zu beseitigen. @jms3000: Coroutinen lösen unter anderem ein Ressourcenproblem was im Grund hier schon angesprochen wurde. Auf dem Rechner auf dem ich das hier gerade tippe kann ich ca. 800 Threads pro Processs starten bevor das System mir sagt, das ist die Obergrenze. Jeder Thread hat seinen eigenen Stack mit je einem Megabyte, also sind da schon mal 800 MiB an Speicher weg, selbst wenn jeder Thread nur ein Anderes Beispiel: Bei Simulationen kann es zu schönerem Code führen wenn man den Code für einzelne Bestandteile als *eine* Coroutine schreibt, statt das auf mehrere Rückrufe aufteilen zu müssen. Es gibt ein interessantes Video von David Beazly wo er Coroutinen mit Generatorfunktionen implementiert. Die sind in Python auch tatsächlich ”verwandt”, denn bevor es |