Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > de.comp.lang.python > #5105
| From | Stefan Behnel <python-de@behnel.de> |
|---|---|
| Newsgroups | de.comp.lang.python |
| Subject | Re: [Python-de] select.epoll() vs async framework (PostgreSQL) |
| Date | 2018-01-20 13:37 +0100 |
| Message-ID | <mailman.167.1516452596.2620.python-de@python.org> (permalink) |
| References | (7 earlier) <bc8618a5-2ffc-46eb-72a4-afa12ddc030d@behnel.de> <374d8f30-d5ec-a097-6bd0-ce15fc0d824e@mail.de> <fea78855-045d-377b-5acd-75de5dab4d18@behnel.de> <0f1b475c-3af3-9baa-860a-1d3baa57f8c3@sschwarzer.net> <b06f8eab-3acf-9191-e470-b0add12a3c33@behnel.de> |
,Stefan Schwarzer schrieb am 20.01.2018 um 00:02:
> On 2018-01-19 11:17, Stefan Behnel wrote:
>> Sven R. Kunze schrieb am 18.01.2018 um 20:02:
>>> On 17.01.2018 22:49, Stefan Behnel wrote:
>>> Das Ineinander-Stöpseln, was ich da erlebt habe, war dann mit so einer
>>> riesigen Menge Boilerplate verbunden, dass ich diese Implementierung nicht
>>> wirklich ernstnehmen konnte. Für mich war es eher nur Show-Case anstelle
>>> einer Implementierung mit Mehrwert. Da wäre ich jetzt wirklich interessiert
>>> an realem Beispielen.
>>
>> Um beim Beispiel zu bleiben, SQLAlchemy lässt sich mit ca. 10-20 Zeilen
>> leicht lesbarem Code mit Hilfe eines Thread-Pools in async-Frameworks
>> integrieren. Dann rufst du statt "query.all()" eben "await
>> background_query(query.all)" auf, und schon blockiert's nicht mehr und
>> erlaubt gleichzeitig 'unbegrenzten' I/O-Durchsatz. Empfinde ich jetzt noch
>> nicht als "riesige Menge Boilerplate", und funktioniert auch mit etlichen
>> anderen thread-sicheren synchronen APIs.
>
> Mich würde es sehr interessieren, diesen "leicht lesbaren"
> Code zu sehen. :-) Ist der auch leicht lesbar für jemanden,
> der mit asyncio noch relativ wenig Erfahrungen hat?
>
> Wie schwierig ist es unter diesen Umständen, selber auf
> diesen Code zu kommen?
Absolut, hier ist die triviale Minimalversion:
"""
size_of_connection_pool = config.get(...)
pool = ThreadPoolExecutor(max_workers=size_of_connection_pool)
def background_query(func, *args, **kwargs):
return pool.submit(func, *args, **kwargs)
"""
Der Produktivcode ist dann doch noch ein bisschen komplexer, eben eher so
20 Zeilen, aber da ist schon wieder einiges Projektspezifische drin. Eine
eigene Funktion (statt direkt den Pool) dafür zu nehmen hat den Vorteil,
dass es a) die komplette Funktionalität hinter einem hübschen Namen
kapselt, und b) der explizite Funktionsname es den Entwicklern schwerer
macht, zu sagen: "och, da ist eh schon ein Thread-Pool, dann kann ich den
ja auch noch für ein paar andere Dinge nehmen". Genau das sollte nämlich
nicht getan werden, um das Ganze kontrollierbar zu halten.
Übrigens ist die DB-Anbindung hier der optimale Fall, weil der
Connection-Pool ja ohnehin eine feste Maximalgröße hat. Da können dann
nochmal genau so viele Threads drauf warten, und fertig.
> Ein weiterer Punkt: Ist das generischer Code, der sich auch
> auf andere synchrone APIs anwenden lässt oder ist es
> überwiegend Code, der speziell auf SQLAlchemy zugeschnitten
> ist?
Das ist genau der Grund, weshalb ich sowas nicht in eine Bibliothek stecken
würde, sondern entweder direkt im Projekt oder in einem Commons-Modul habe.
Oft lohnt es sich, die Funktionalität in der Wrapperfunktion ein bisschen
auszuweiten. Findet sich eigentlich immer was, sei es spezifisches Logging,
Fehlerbehandlungen, oder was auch immer. Dann wird die "background_query"
Funktion eben selbst zu einer async-def Koroutine und loggt nach dem Aufruf
z.B. noch die Ausführungsdauer raus.
> Ein paar Gedanken zur Vermittlung von Asyncio in Python:
>
> Die meisten Artikel, die ich dazu bisher gesehen habe, sind
> Einführungen anhand relativ einfacher Beispiele. Um den Leser
> nicht gleich zu "erschrecken", werden Aufgabe und Lösung so
> gewählt, dass sie gut ins Asyncio-Paradigma bzw. den Support
> dafür in Python passen.
>
> Aber wie geht es weiter, wenn man sich nicht nur einen Überblick
> verschaffen, sondern Pythons Asyncio in "richtigen" Projekten
> einsetzen will? Da habe ich bisher extrem wenig Dokumentation
> zu gefunden. Ich glaube, das hier geht in die richtige Richtung:
> https://pymotw.com/3/asyncio/index.html .
Na ja, in den seltensten Fällen wirst du asyncio direkt verwenden. Für
alles Web-basierte (Webseiten oder REST-APIS) gibt es z.B. Tornado, aiohttp
oder Twisted-Web als Frameworks (Tornado und Twisted sind übrigens auch
schon deutlich älter als asyncio, können es aber inzwischen verwenden).
> Auf der anderen "Seite" von Einsteiger-Tutorials finden sich die
> technisch ausführliche Dokumentation wie die unter
> https://docs.python.org/3/library/asyncio.html . Da mag zwar alles
> drin stehen, aber als Einsteiger sieht man den Wald vor lauter
> Bäumen nicht. Das ist, wie eine natürliche Sprache aus einem
> Wörterbuch und einer Grammatik-Referenz lernen zu wollen.
Das Gute ist: die Doku kannst du als Normalnutzer fast komplett ignorieren.
Betrachte asyncio am besten als Infrastrukturbaustein. So, wie es mit dem
ThreadPoolExecutor eine hübsche API für das threading-Modul gibt, gibt es
auch entsprechende Frameworks und Tools, die auf asyncio aufsetzen. Vor
allem Twisted ist ja ein großer Baukasten mit allen möglichen
Netzwerkprotokollen, bis hin zu SMTP und SSH, und Tornado und aiohttp sind
schicke Web-Frameworks.
Das Gute an asyncio und async/await gegenüber der Zeit davor, ist, dass all
diese Frameworks nun kompatibel werden, und viel async-Code sich über
Framework-Grenzen hinweg wiederverwenden lässt. Übrigens ganz ähnlich wie
bei Generatoren in Python. Damit lässt sich auch vieles generisch und
wiederverwendbar implementieren.
> Stefan, ich hatte auch schon überlegt, dir vorzuschlagen, dazu
> einen Vortrag auf der PyCon DE zu halten. Ich fürchte aber, dass
> ein Vortrag nicht das richtige Medium dafür ist bzw. in der
> verfügbaren Zeit kaum über eine Asyncio-Einführung hinauskommt.
> Was meinst du?
Na ja, das Problem ist so ein bisschen: es gibt da ja eigentlich nur wenig
Neues. Ja, es funktioniert jetzt so langsam alles mit allem, aber das, was
da funktioniert, das funktioniert eigentlich auch schon länger. Wo also ist
da das interessante Thema für einen Vortag, der den ZuhörerInnen inhaltlich
auch irgendwas bringt? Asyncio ist da halt die falsche Ebene, und zwar die
völlig falsche.
Die meisten async-Anwendungen, an denen ich gearbeitet habe, verwenden
Tornado, ein paar statt dessen Twisted oder aiohttp. Dazu verschiedene
Datenbanken und andere Backends, externe Services, Queues oder was weiß ich
was. Der I/O-Loop selbst ist auf der Ebene völlig irrelevant, der muss nur
einfach da sein und funktionieren. Und der Anwendungscode sieht dann auch
nicht wesentlich anders aus als anderswo auch, nur eben mit async/await
oder Py2-Koroutinen statt "normalen" Funktionen.
Das ist ja gerade der Vorteil von async/await, dass sich eigentlich nichts
an der Programmierung ändert. Nur Kommunikation und Delegation wird eben
mit "await" (oder "yield" in Py2) explizit sichtbar im Code. Und die
komplette Anwendung läuft weiterhin single-threaded, vermeidet also
Race-Conditions und die ganzen Multi-Threading-Fußfallen, die auftreten,
sobald ich (aus Effizienzgründen) irgendwelchen Zustand global halten muss.
Stefan
Back to de.comp.lang.python | Previous | Next | Find similar
Re: [Python-de] select.epoll() vs async framework (PostgreSQL) Stefan Behnel <python-de@behnel.de> - 2018-01-20 13:37 +0100
csiph-web