Vielen Dank, dass ihr weiter versucht meine Fragen zu beantworten.
Leider ist es noch nicht geglückt.
Offtopic:
@__deets__:
Ist vermutlich ein ähnliches verständnisproblem wie bei requests.session() ? Ich glaube der Kerngedanke von blackjack war, dass er mir sagen wollte, dass es diese funktion nicht mehr gibt und man stattdessen die Klasse Session() verwenden soll. Ich habe es aber so verstanden, dass es das ganze session objekt nicht mehr gibt. Hätte er es simpel ausgdrückt und gesagt "schreib das groß, kleinschreiben ist veraltet", dann wäre alles klar gewesen.
Auf threads bezogen: Auch du denkst als Experte so kompliziert, dass ich als Amateur nicht folgen kann. Ohne es zu verstehen, in der Annahme, dass du recht hast -> beide verfolgen dasselbe Ziel mit unteschiedlichen Ansätzen. Async ist sehr kompliziert, daher möchte ich als Anfänger klein anfangen und es nicht auf alles anwenden, sondern nur auf den Teil, der mir einleuchtet. Selbst wenn es tatsächlch möglich sein sollte, mein Programm komplett in async zu schreiben, so kann ich das als Anfänger nicht mal eben stemmen, ich muss klein anfangen. Deshalb der absolut natürliche Wunsch, es erstmal nur für die wichtigste Stelle, den REST API Calls zu verwenden, da async hier den threads eindeutig überlegen ist.
@blackjack:
bzgl requests.session siehe oben. Bzgl Vergleich threads vs async: Allein von der Logik ist es schon offensichtlich ,dass async besser ist, je mehr calls es werden. Aber natürlich habe ich das auch schon selbst probiert und async um ein vielfaches besser. Einen niedergeschrieben test dazu findest du hier: https://stackabuse.com/asynchronous-vs- ... -analysis/
@DeaD_EyE:
Dankesehr, du bist damit der erste, der tatsächlich versucht meine Frage im Eröffnungspost zu beantworten.
Leider war ich selber dann schon so weit gekommen und habe ja bereits code mit async vorgestellt. Bei diesem stellt sich die frage, warum dort Fehler auftreten, bzw warum es scheinbar unmöglich ist, die session außerhalb einer async funktion zu erstellen, obwohl empfohlen wird, dieselbe session für die betriebszeit beizubehalten. Den Link habe ich gelesen und entnehme daraus die Info, wie die gather funktion funktioniert und dass sie eigentlich die einzige Funktion (von den vorgestellten) von asyncio ist, die ich brauche.
@Sirius3:
Es ist nicht leicht als Anfänger Hilfe zu finden. Ich habe mich das gesamte Wochenende mit asyncio beschäftigt, aber konnte es selbstständig nicht verstehen, weshalb ich mich ans Forum gewandt habe. Der Hauptgrund, weshalb ich die üblichen Tutorials als "nicht hilfreich" verwerfe, ist eben dass dort alles async ist. Ich will ja aber sync und async mixen, was aus mir schleiferhaften Gründen ein Sakrileg ist. Aber wie im Abschnitt zu __deets__ geschrieben, nehme ich das gerne als gegeben hin, dennoch sehe ich (und viele andere wie ich bei meiner google suche festgestellt habe) dennoch eine Notwendidkeit in manchen Fällen threads und async zu kombinieren.
Ich habe deinen verlinkten Artikel gelesen und würde behaupten, dass ich fast alles davon bereits wusste. Die angebliche Begrenzung auf 1024 http Calls war mir neu, allerdings kann ich dies in eigenen Tests nicht bestätigen, bei mir laufen auch 1025 calls nach example.com problemlos .
Wie ich nun weitermachen werde:
Mir fehlt noch die Weitsicht, um euch zu verstehen. Deshalb mache ich es wie die meisten Anfänger (und so wie ich es seit 8 Jahren "erfolgreich" mache): Ich schreibe es so, wie ich es aktuell für richtig halte. In ein paar Monaten oder Jahren, werde ich durch eigene Erfahrungen dann merken, dass es garnicht so schlau war, was mir dabei hilft eure Argumentation nachzuvollziehen. Dies war damals schon bei Verwendung von loops und anderen Themen, weshalb ich denke, dass dies die sinnvollste Vorgehensweise für mich ist.
Topic:
Die Fehler im Traceback meines entprechenden Posts kommen übrigens allein dadurch zustande, dass ich asyncio.run() mehrfach (aber nacheinander) aufrufe. Sobald es das zweite mal aufgerufen wird, gibts einen riesen traceback, der alles mögliche enthält, aber nicht die eigentliche Fehlerursache (das doppelte Aufrufen). Keine Ahung warum ein doppeltes Aufrufen nacheinander ein Problem darstellt.
-> dies bitte unkommentiert lassen, wenn ihr dazu keine für mich nachvollziehbare Erklärung oder fertige Lösung habt, wie ich mehr erfahre. Es dient nur dazu aufzuschreiben, was ich selbst durch Probieren rausgefunden habe und vielleicht hilft es entweder mir oder einem anderen Anfänger, der dieselben Probleme hat:
Nehmen wir es mal als gegeben hin, dass wir asyncio.run() nur ein einziges mal aufrufen dürfen, dann scheint die Lösung wohl zu sein, dass ich einen Thread brauche, welcher dauerhaft diesen einen asyncio loop auführt.
Ähnlich dem Code hier: https://stackoverflow.com/questions/601 ... e-function
In diesem Link lässt sich auch nachlesen, dass meine Gedankengänge nicht"völlig absurd" sind, sondern dass viele so denken, denn der Antwortende zählt genau meinen Grund auf, warum man threads und async mixen wollen würde (large code den man nicht mal eben so in async umwandeln kann).
Ich würde mich sehr freuen, wenn ihr ungeachtet dessen, dass es keine gute Praxis ist threads mit async zu mischen, mir dennoch helft genau dies mithilfe von asyncio.run_coroutine_threadsafe zu realisieren.
Die nächsten Stunden werde ich nun genau daran rumbasteln und bin zuversichtlich, dass ich das irgendwie hinbekomme. Wenn ihr mich aber dabei unterstützen könntet, wäre das schön.
thread code in async umwandeln?
Natürlich kannst Du async und sync mischen, aber sync blockiert dann eben. Wenn Du in Deinem Programm zwei Phasen hast, erstens Daten sammeln (io-bound) und dann auswerten (cpu-bound), kann das durchaus praktikabel sein. Hier als Idee:
Bei 'await gather_data' pausiert die Ausführung von main, während asynchron die Tasks zum Daten sammeln ausgeführt werden. Bei 'process' erfolgt der Aufruf dann synchron, d.h. die asyncio main-loop ist blockiert. Aber solange das Programm selbst kein REST-Service ist (die latency keine Rolle spielt) kannst Du damit leben und brauchst auch keine Threads. Eher multiprocessing, um die Auswertung zu beschleunigen, insofern das die Daten erlauben.
Code: Alles auswählen
import asyncio
async def main():
data = await gather_data() # get data asynchrone
process(data) # synchrone, but may be with multiprocessing
asyncio.run(main())
Du hast wiederholt behauptet, Threads wuerden gleichzeitig laufen, wie zB hier:
Die Bezeichnung "Sakrileg" ist dabei aber nie gefallen, und wenn du hier ein Plaedoyer fuer Ruecksichtnahme und gewaltfreie Kommunikation machst, aber selbst zu Ueberspitzungen greifst - dann passt da was nicht.
Auf diese Behauptung habe ich mich bezogen, wenn ich sage, dass Threads in Python nunmal nicht gleichzeitig laufen. Das ist auch nicht kompliziert. Das ist einfach ein Fakt. Damit kannst du jetzt erstmal machen was du willst. Ich persoenlich bin der Meinung, das die Kombination von zwei offensichtlich nicht wirklich verstaendenen Techniken im Produkt nochmal schwerer zu Raeson zu bringen ist, als sich eben nur auf eine zu stuerzen. Gerade Threads sind immer haerter, als man das glaubt. Musst du mir nicht glauben, die Erfahrung kannst du auch selbst machen.Scholastik hat geschrieben: Montag 3. August 2020, 21:20 dennoch müssen alle instanzen zeitgleich laufen, nicht nacheinander.
Daher sehe ich hier wirklich keinen einzigen guten Grund, weshalb ich alles auf async umschreiben sollte?!
Die Bezeichnung "Sakrileg" ist dabei aber nie gefallen, und wenn du hier ein Plaedoyer fuer Ruecksichtnahme und gewaltfreie Kommunikation machst, aber selbst zu Ueberspitzungen greifst - dann passt da was nicht.
- __blackjack__
- User
- Beiträge: 14007
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Scholastik: Der verlinkte Vergleich taugt IMHO nicht so. Ich habe mir nur die Clients angeschaut und die machen nicht das gleiche. Der eine lädt auch den Body der Antwort, der andere nicht. Damit ist das Ergebnis schon nicht mehr so wirklich sinnvoll vergleichbar.
Die Behauptung das der Thread-Quelltext komplexer ist und deshalb auch bei ähnlichen Ergebnissen schlechter sei ist IMHO ziemlich an den Haaren herbeigezogen. Wenn man das parsen von Kommendozeilenargumenten da raus nimmt und sich nicht selbst was bastelt was es in `concurrent.futures` bereits fertig gibt, ist der Code nicht komplexer. Das muss man schon absichtlich komplex schreiben.
Was bei dem async-Code fehlt, und worüber Du Dir wohl auch tatsächlich in Deinem Anwendungsfall Gedanken machen musst, ist die Beschränkung auf eine gewisse Anfragen parallel. Es wurde hier im Thema schon von jemanden erwähnt: Sockets sind nicht unbegrenzt verfügbar.
Die 1024 sehe ich hier nirgends als Grenze. Die Grenzen legt jeder API-Anbieter selbst fest. Manchmal steht das in der Dokumentation der API manchmal auch nicht. Manchmal muss es nicht einmal eine API sein, sondern es werden auch IPs gesperrt die in zu kurzer Zeit zu viele Anfragen auch auf normale Webseiten stellen. Example.com wird das eher nicht machen, zumindest bei dynamischen Inhalten die Rechenzeit kosten aber auch nicht ausgeschlossen.
Eine Erklärung warum `asyncio.run()` mehrfach aufgerufen Probleme macht, braucht man nicht wirklich. Es sollte reichen das die Dokumentation von der Funktion deutlich sagt „It should be used as a main entry point for asyncio programs, and should ideally only be called once.“
Die Behauptung das der Thread-Quelltext komplexer ist und deshalb auch bei ähnlichen Ergebnissen schlechter sei ist IMHO ziemlich an den Haaren herbeigezogen. Wenn man das parsen von Kommendozeilenargumenten da raus nimmt und sich nicht selbst was bastelt was es in `concurrent.futures` bereits fertig gibt, ist der Code nicht komplexer. Das muss man schon absichtlich komplex schreiben.
Was bei dem async-Code fehlt, und worüber Du Dir wohl auch tatsächlich in Deinem Anwendungsfall Gedanken machen musst, ist die Beschränkung auf eine gewisse Anfragen parallel. Es wurde hier im Thema schon von jemanden erwähnt: Sockets sind nicht unbegrenzt verfügbar.
Die 1024 sehe ich hier nirgends als Grenze. Die Grenzen legt jeder API-Anbieter selbst fest. Manchmal steht das in der Dokumentation der API manchmal auch nicht. Manchmal muss es nicht einmal eine API sein, sondern es werden auch IPs gesperrt die in zu kurzer Zeit zu viele Anfragen auch auf normale Webseiten stellen. Example.com wird das eher nicht machen, zumindest bei dynamischen Inhalten die Rechenzeit kosten aber auch nicht ausgeschlossen.
Eine Erklärung warum `asyncio.run()` mehrfach aufgerufen Probleme macht, braucht man nicht wirklich. Es sollte reichen das die Dokumentation von der Funktion deutlich sagt „It should be used as a main entry point for asyncio programs, and should ideally only be called once.“
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
HTTP clients nutzen in der Regel Pools von Verbindungen, da es ziemlich teuer ist für jeden Request eine neue Verbindung aufzubauen, dass erhöht die Latency und auch Throughput ist bei einer neuen Verbindung erstmal nicht so toll. So ein Pool ist so dimensioniert dass man sich da normalerweise keine Gedanken drum machen muss aber bei so vielen parallelen Requests, macht es Sinn sich die Konfiguration mal anzuschauen.
Requests hat z.B. maximal nur 10 Verbindungen im Pool, etwas wenig wenn man parallel 1000 Requests machen möchte.
Ansonsten Threads mit asyncio zu kombinieren ist vollkommen sinnlos und kompliziert. Beides löst dasselbe Problem in Python, durch die Kombination handelst du dir unnötige Context Switches ein, die dein ganzes Program langsamer machen. Wenn du nicht überall im Program asyncio nutzen möchtest, würde ich den asyncio Teil einfach separat halten. Dies lässt sich z.B. machen indem du eine Funktion die intern asyncio nutzt mit einem Dekorator wie diesem dekorierst, die Funktion kannst du dann wie eine "normale" Funktion aufrufen ohne dass der Aufrufer etwas über asyncio wissen muss.
Requests hat z.B. maximal nur 10 Verbindungen im Pool, etwas wenig wenn man parallel 1000 Requests machen möchte.
Ansonsten Threads mit asyncio zu kombinieren ist vollkommen sinnlos und kompliziert. Beides löst dasselbe Problem in Python, durch die Kombination handelst du dir unnötige Context Switches ein, die dein ganzes Program langsamer machen. Wenn du nicht überall im Program asyncio nutzen möchtest, würde ich den asyncio Teil einfach separat halten. Dies lässt sich z.B. machen indem du eine Funktion die intern asyncio nutzt mit einem Dekorator wie diesem dekorierst, die Funktion kannst du dann wie eine "normale" Funktion aufrufen ohne dass der Aufrufer etwas über asyncio wissen muss.
Code: Alles auswählen
import asyncio
import functools
def coroutine_to_function(coroutine):
assert asyncio.iscoroutinefunction(coroutine)
@functools.wraps(coroutine)
def wrapper(*args, **kwargs):
return asyncio.run(coroutine(*args, **kwargs))
return wrapper
@coroutine_to_function
async def foo():
await asyncio.sleep(1)
return "bla"
def main():
assert foo() == "bla"
- noisefloor
- User
- Beiträge: 4179
- Registriert: Mittwoch 17. Oktober 2007, 21:40
- Wohnort: WW
- Kontaktdaten:
Hallo,
@Scholastik: nimm' dir mal die Zeit und beschäftigen dich mit dem CPython GIL. Z.B. hier: https://www.dabeaz.com/python/UnderstandingGIL.pdf
Bzw. sehr sehenswert sind auch die Vortragsvideos dazu:
https://www.youtube.com/watch?v=Obt-vMVdM8s
https://www.youtube.com/watch?v=ph374fJqFPE
Dann verstehst du (hoffentlich) auch, dass der Ansatz aus dem Ausgangsthread, mit 1000 oder nur 500 Threads nicht so clever ist.
Bzgl. asyncio:
Es gibt mehrere Frameworks, die asyncron arbeiten und HTTP-Request schicken. httpx unds aiohttp wurden schon genannt, asks ist ein weiteres. Bei asks findest du auch Beispielcode, der genau das macht, was du suchst. Und das gibt es auch ein Beispiel inkl. Erklärung wo steht, warum es nicht clever ist, hunderte von Request gleichzeitig raus zu hauen.
Aber solange du an Threads + asyncio festhalten willst, also zumindest in der Form, wie du es hier kommunizierst, wirst du eher an deiner eigenen Einstellung scheitern als an Python und asyncio.
Gruß, noisefloor
@Scholastik: nimm' dir mal die Zeit und beschäftigen dich mit dem CPython GIL. Z.B. hier: https://www.dabeaz.com/python/UnderstandingGIL.pdf
Bzw. sehr sehenswert sind auch die Vortragsvideos dazu:
https://www.youtube.com/watch?v=Obt-vMVdM8s
https://www.youtube.com/watch?v=ph374fJqFPE
Dann verstehst du (hoffentlich) auch, dass der Ansatz aus dem Ausgangsthread, mit 1000 oder nur 500 Threads nicht so clever ist.
Bzgl. asyncio:
Es gibt mehrere Frameworks, die asyncron arbeiten und HTTP-Request schicken. httpx unds aiohttp wurden schon genannt, asks ist ein weiteres. Bei asks findest du auch Beispielcode, der genau das macht, was du suchst. Und das gibt es auch ein Beispiel inkl. Erklärung wo steht, warum es nicht clever ist, hunderte von Request gleichzeitig raus zu hauen.
Aber solange du an Threads + asyncio festhalten willst, also zumindest in der Form, wie du es hier kommunizierst, wirst du eher an deiner eigenen Einstellung scheitern als an Python und asyncio.
Gruß, noisefloor
-
- User
- Beiträge: 53
- Registriert: Freitag 7. Juli 2017, 12:35
Sooo.... mittlerweile habe ich denke ich sehr sehr viel über asyncio und threads dazugelernt, auch dass threads scheinbar ähnlich wie asyncio zwischen den unterschiedlichen tasks hin und herspringen (daher nicht wirklich gleichzeitig laufen, wie von euch schon angemerkt) und bei asyncio wohl der einzige vorteil zu sein scheint, dass man mit await den Zeitpunkt des "springens zur anderen task" selbst bestimmen kann.
So habe ich auch mit asyncio.run_coroutine_threadsafe experimentiert (welches übrigens die Antwort auf meine Ursprungsfrage gewesen wäre, es macht genau das, was ich gefragt habe) und weiß auch wie richtiger async code funktioniert. Leider bringt die Mischung von threads und asyncio so seine Problemchen mit, wie ihr ja schon erwähnt habt. Dennoch ist es weiterhin offensichtlich, dass man threads/prozesse und asyncio mischen muss, um gute Ergebnisse zu erzielen, wenn man was richtiges coded (nicht nur codeschnipsel), denn es gibt immer irgendwo cpu intensiven code, spätestens diesen muss man, wie sogar in der asyncio doku empfohlen mit run_in_executor ausführen.
Wegen diverser Problemchen (mit asyncio.run_coroutine_threadsafe), weiß ich noch nicht wie ich mein aktuelles Programm nun sinnvoll umändern kann (habe schon sehr viele Entwürfe gemacht und verworfen). Leider findet man nur wirklich ziemliche basics an tutorials, die mich aber nicht weiter bringen.
Daher möchte ich nun gerne euren Ansatz hören, wie ihr folgendes umsetzen würdet. Darunter meine Ideen dazu.
Ich versuche einmal kurz zu erklären, was das Programm tun soll:
Nennen wir es beim Namen es ist ein Tradingbot-system. Ich möchte unterschiedliche Tradingstrategien entwickeln und gleichzeitg laufen lassen. Der aktuelle Aufbau sieht vor, ein eigenes Skript und Klasse je Strategie (Bot) zu verwenden und auch einen eigenen Prozess/Thread, damit sie gleichzeitig laufen können. Der Aufbau ist so gewählt, weil es von der Struktur so am einfachsten und übersichtlichsten ist, also jede Strategie bekommt eine eigene Klasse und läuft für sich. Dennoch sollen die Bots grob wissen, was der andere gemacht hat und sie sollen auch dieselbe API Verbindung zu den websiten nutzen. Daher habe ich eine sogenannte BotManager (BotMan) Klasse eingeführt. Diese wird zu Beginn des Systems aufgerufen und startet die ganzen Strategieskripte (Bots) und übergibt sich selbst. Die Bots können dann jegliche API Anfragen die sie haben an den BotMan übergeben, dieser wandelt die Anfragen vorher noch in das richtige Format um und leitet sie dann an die websiten weiter und speichert gegebenenfalls auch einige Infos selbst ab, auf die dann andere Bots Zugriff haben (zb eine Art cache um denselben Call nicht mehrfach in kurzer Zeit machen zu müssen)
Auf diese Weise können die Bots alle in einem einheitlichen Format arbeiten und brauchen sich um nicht viel weiter kümmern, da der BotMan alles (zb auch Fehlerhandling) übernimmt. Auch lassen sich problemlos neue Strategien entwickeln/testen, ohne dass man irgendwo etwas "zwischenschieben oder integrieren" muss, einfach ein neues skript und Klasse und sogut wie fertig. Ich hoffe das war einigermaßen verständlich, was das Ziel ist.
Aktuell starte ich zu Beginn für jeden Bot (=Strategie) also einen eigenen Prozess/Thread (die problematik von Prozessen lassen wir erstmal außen vor). Dabei ist es mir erstmal nicht direkt wichtig, ob diese Bots nun wirklich gleichzeitig laufen, oder ob es durch das threading-hin-und-herspringen nur den Anschein von Gleichzeitigkeit hat. Wichtig ist, dass es mit threading besser ist, als es sequentiell auszuführen (auch wenn die gesamt durchlaufzeit bei reinem code natürlich dieselbe bliebe). Die Bots sollen wie gesagt beliebige Strategien fahren können, vom high-frequency-trading bis hin zu "einmal am Tag den Kurs checken", soll dem Strategieskript überlassen sein.
Die API Calls, welche nun also jederzeit von jedem dieser Bots zu jedem Zeitpunkt und jeder Anzahl beim BotMan angefragt werden können, mache ich zurzeit ebenfalls noch in threads. Pro API call wird nun also ein weiterer Thread geöffnet. Nun soll das ganze aber stark skalieren, mit sehr vielen untersch. websiten und sehr vielen untersch. calls und strategien (die limits der websiten habe ich dabei natürlich im Blick), weshalb ich in meinen Tests auf das Maximallimit an threads gestoßen bin.
Da ich kein System schreiben will, welches die maximale Anzahl an Threads überwacht (oder halt ein Pool mit 500 Threads fürs gesamte System) und ich glaube, dass 500 Threads ohnehin zuviele sind und das effizienter gehen muss, habe ich angefangen mich mit asyncio zu beschäftigen. Dabei habe ich den Ansatz verfolgt "das System soll so bleiben wie es aktuell ist (denn ich habe daran mittlerweile 3 Jahre gearbeitet), aber die unnötigen Threads bei den API calls sollte man durch asyncio ersetzen. Rein logisch ist das der Beste Ansatz, denn nur bei diesen API Calls bringt asyncio was. Der Rest ist CPU intensiver Code, bei dem asyncio nichts bringt. 1000 apicalls über asyncio sind schneller sind als 1000 apicalls über einen threadpool mit 500 threads. Selbst bei 500 calls und 500 threads wäre das so, aber das kann möglicherweise auch daran liegen, dass aiohttp schneller ist, als requets. Natürlich werde ich nicht regelmäßig 1000 API calls gleichzeitig machen. Aber es kann zu spitzenzeiten durchaus passieren und ich will das nicht überwachen müssen, bzw durch einen begrenzten pool unnötig verzögerungen haben.
Alles sync, nur api calls async mit run_coroutine_threadsafe:
Also habe ich nun run_coroutine_threadsafe verwendet, um kurz bevor der API-call gemacht wird, anstatt requests, aiohttp aufzurufen. Also nur das allernötigste wird innerhalb des eventloops gemacht. Leider bin ich dabei nun auf diverse Probleme gestoßen, die ich scheinbar nur lösen kann, indem ich doch einiges an Struktur verändere, was ich aber nicht möchte. Falls ihr auch der Meinung seid, dass dieser Ansatz der Beste für mein System ist, kann ich auf die Probleme näher eingehen.
Alles async, nur längeren/cpu instensiven code mit run_in_executor:
Aber wegen der Probleme habe ich nun versucht mir zu überlegen, wie denn nun ein echter guter async Programmierer das System umsetzen würde und ob das vielleicht besser wäre. Vermutlich würde er, wie man es überall so liest, das ganze system auf async umstellen. Das heißt prinzipiell läuft erstmal alles im async loop. Für blockenden/cpu intensiven code gibt es dann die asyncio.run_in_executor Funktione, welche diesen code dann in Threads/Prozesse auslagert, um den event loop nicht zu blockieren. Nur besteht mein System aus ca. 90% (teils cpu intensivem) Code und 10% API call - code (grob geschätzt). Das heißt 90% meines codes würde dann durch run_in_executor durchgeführt, womit wir theoretisch doch wieder beim run_coroutine_threadsafe System sind. Es ist quasi dasselbe nur invertiert.
Nun kann man aber argumentieren, anstatt jede einzelne strategie==Bot in einem eigenen Prozess laufen zu lassen, wäre es möglicherweise schon effizienter nur für langen wirklich cpu intensiven code diese Prozess-Keule (übertragung zwischen prozessen bedeuted deepcopy und das bedeutet zeitverlust) rauszuholen und für die kürzeren code abschnitte threads zu nehmen. So wäre die Ressourcennutzung von dem speziell aufgerufenem code abhängig und nicht von dem skript und somit potentiell sinnvoller aufgeteilt, weshalb dieser Weg theoretisch also besser wäre, als "sync" code mit run_coroutine_threadsafe zu verwenden... ganz besonders wenn es zb um eine strategie geht, die zb nur stündlich aktiv werden soll (soll ja aber wie erwähnt jede strategie möglich sein)
Angenommen dieser Ansatz ist also der bessere: Wie sieht meine Struktur dann aus? Wenn ich im Strategieskript also eine Funktion habe, welche, um ein exaktes beispiel zu nennen, das platzieren einer order vorbereitet, den API call dafür macht, die antwort auswertet, dann einen weiteren api call macht um weitere infos über die order zu erlangen, die antwort auswertet und die order dann gegebenenfalls wieder cancelt (vorbereitung, api call und nachbereitung). Diese Operation gehört zusammen (eigene umsetzung eines fill_or_kill auftrags), es würde also zu massiver verwirrung und viel viel viel zuvielen Funktionen führen, wenn jeder codeabschnitt vor/nach den calls in eine eigene Funktion kommen würde, damit ich sie mit run_in_executor aufrufen könnte. Ich kann aber auch nicht einfach alles im eventloop durchführen, da dies bei vielen solcher anfragen ja skaliert und den event loop so sehr lange blockieren könnte.
Daher scheint mir die Version mit run_in_executor auch nicht wirklich sinnvoll, oder was übersehe ich?
Ganz abgesehen davon, dass ich nun nicht mehr wüsste, wie die unterschiedlichen Strategien nun aufgebaut (eigenes skript pro strategie, geht das noch ?) und laufen würden.
eventbasiert:
Natürlich kenne ich bereits ein paar "Backtesting" module. Diese laufen eventbasiert (was sich bei asyncio glaube ich anbietet), dh. in jedem Tick wird eine hauptfunktion im strategieskript aufgerufen und zb. die aktuellen Preisdaten übergeben. Das strategieskript kann damit dann machen was es will.
Sowas köööönnte man theoretisch auch für mein system verwenden, aber ich hatte bisher nicht das gefühl, dass das so einfach funktioniert. Wenn ich websocket verwende, dann bekomme ich extrem viele Preisdaten pro Sekunde, die kann ich unmöglich alle als Event weiterleiten.
Also bräuchte ich etwas, das zb stattdessen einmal pro Sekunde die aktuellen Preisdaten weiterleitet. Aber es soll ja für beliebige Strategien funktionieren. Sollten dann nur langsame Strategien angeschlossen sein, wäre es zuviel des Guten. Sollte eine schnellere Strategie angeschlossen sein, wäre es zu wenig.
Daher dachte ich, es wäre besser, wenn jeder Bot selbst seinen Rythmus bestimmt und aktiv nach den aktuellen Preisdaten fragt, wenn er sie braucht. Oder widerspricht sich das alles nicht und kann dennoch umgesetzt werden?
Ich möchte euch bitten nur zu antworten, wenn ihr direkt auf das beschriebene System "was soll es tun" eingeht und wirklich Lösungsvorschläge dazu habt.
Gerne können wir das ganze auch in einem Voicechat besprechen und wenn gewünscht zahle ich auch für diese "Unterrichtsstunde".
Aber ich brauche hier wirklich auf meinen speziellen Fall abgepasste Antworten. Allgemeine Antworten oder "lies dir xy durch" helfen hier sicherlich nicht.
So habe ich auch mit asyncio.run_coroutine_threadsafe experimentiert (welches übrigens die Antwort auf meine Ursprungsfrage gewesen wäre, es macht genau das, was ich gefragt habe) und weiß auch wie richtiger async code funktioniert. Leider bringt die Mischung von threads und asyncio so seine Problemchen mit, wie ihr ja schon erwähnt habt. Dennoch ist es weiterhin offensichtlich, dass man threads/prozesse und asyncio mischen muss, um gute Ergebnisse zu erzielen, wenn man was richtiges coded (nicht nur codeschnipsel), denn es gibt immer irgendwo cpu intensiven code, spätestens diesen muss man, wie sogar in der asyncio doku empfohlen mit run_in_executor ausführen.
Wegen diverser Problemchen (mit asyncio.run_coroutine_threadsafe), weiß ich noch nicht wie ich mein aktuelles Programm nun sinnvoll umändern kann (habe schon sehr viele Entwürfe gemacht und verworfen). Leider findet man nur wirklich ziemliche basics an tutorials, die mich aber nicht weiter bringen.
Daher möchte ich nun gerne euren Ansatz hören, wie ihr folgendes umsetzen würdet. Darunter meine Ideen dazu.
Ich versuche einmal kurz zu erklären, was das Programm tun soll:
Nennen wir es beim Namen es ist ein Tradingbot-system. Ich möchte unterschiedliche Tradingstrategien entwickeln und gleichzeitg laufen lassen. Der aktuelle Aufbau sieht vor, ein eigenes Skript und Klasse je Strategie (Bot) zu verwenden und auch einen eigenen Prozess/Thread, damit sie gleichzeitig laufen können. Der Aufbau ist so gewählt, weil es von der Struktur so am einfachsten und übersichtlichsten ist, also jede Strategie bekommt eine eigene Klasse und läuft für sich. Dennoch sollen die Bots grob wissen, was der andere gemacht hat und sie sollen auch dieselbe API Verbindung zu den websiten nutzen. Daher habe ich eine sogenannte BotManager (BotMan) Klasse eingeführt. Diese wird zu Beginn des Systems aufgerufen und startet die ganzen Strategieskripte (Bots) und übergibt sich selbst. Die Bots können dann jegliche API Anfragen die sie haben an den BotMan übergeben, dieser wandelt die Anfragen vorher noch in das richtige Format um und leitet sie dann an die websiten weiter und speichert gegebenenfalls auch einige Infos selbst ab, auf die dann andere Bots Zugriff haben (zb eine Art cache um denselben Call nicht mehrfach in kurzer Zeit machen zu müssen)
Auf diese Weise können die Bots alle in einem einheitlichen Format arbeiten und brauchen sich um nicht viel weiter kümmern, da der BotMan alles (zb auch Fehlerhandling) übernimmt. Auch lassen sich problemlos neue Strategien entwickeln/testen, ohne dass man irgendwo etwas "zwischenschieben oder integrieren" muss, einfach ein neues skript und Klasse und sogut wie fertig. Ich hoffe das war einigermaßen verständlich, was das Ziel ist.
Aktuell starte ich zu Beginn für jeden Bot (=Strategie) also einen eigenen Prozess/Thread (die problematik von Prozessen lassen wir erstmal außen vor). Dabei ist es mir erstmal nicht direkt wichtig, ob diese Bots nun wirklich gleichzeitig laufen, oder ob es durch das threading-hin-und-herspringen nur den Anschein von Gleichzeitigkeit hat. Wichtig ist, dass es mit threading besser ist, als es sequentiell auszuführen (auch wenn die gesamt durchlaufzeit bei reinem code natürlich dieselbe bliebe). Die Bots sollen wie gesagt beliebige Strategien fahren können, vom high-frequency-trading bis hin zu "einmal am Tag den Kurs checken", soll dem Strategieskript überlassen sein.
Die API Calls, welche nun also jederzeit von jedem dieser Bots zu jedem Zeitpunkt und jeder Anzahl beim BotMan angefragt werden können, mache ich zurzeit ebenfalls noch in threads. Pro API call wird nun also ein weiterer Thread geöffnet. Nun soll das ganze aber stark skalieren, mit sehr vielen untersch. websiten und sehr vielen untersch. calls und strategien (die limits der websiten habe ich dabei natürlich im Blick), weshalb ich in meinen Tests auf das Maximallimit an threads gestoßen bin.
Da ich kein System schreiben will, welches die maximale Anzahl an Threads überwacht (oder halt ein Pool mit 500 Threads fürs gesamte System) und ich glaube, dass 500 Threads ohnehin zuviele sind und das effizienter gehen muss, habe ich angefangen mich mit asyncio zu beschäftigen. Dabei habe ich den Ansatz verfolgt "das System soll so bleiben wie es aktuell ist (denn ich habe daran mittlerweile 3 Jahre gearbeitet), aber die unnötigen Threads bei den API calls sollte man durch asyncio ersetzen. Rein logisch ist das der Beste Ansatz, denn nur bei diesen API Calls bringt asyncio was. Der Rest ist CPU intensiver Code, bei dem asyncio nichts bringt. 1000 apicalls über asyncio sind schneller sind als 1000 apicalls über einen threadpool mit 500 threads. Selbst bei 500 calls und 500 threads wäre das so, aber das kann möglicherweise auch daran liegen, dass aiohttp schneller ist, als requets. Natürlich werde ich nicht regelmäßig 1000 API calls gleichzeitig machen. Aber es kann zu spitzenzeiten durchaus passieren und ich will das nicht überwachen müssen, bzw durch einen begrenzten pool unnötig verzögerungen haben.
Alles sync, nur api calls async mit run_coroutine_threadsafe:
Also habe ich nun run_coroutine_threadsafe verwendet, um kurz bevor der API-call gemacht wird, anstatt requests, aiohttp aufzurufen. Also nur das allernötigste wird innerhalb des eventloops gemacht. Leider bin ich dabei nun auf diverse Probleme gestoßen, die ich scheinbar nur lösen kann, indem ich doch einiges an Struktur verändere, was ich aber nicht möchte. Falls ihr auch der Meinung seid, dass dieser Ansatz der Beste für mein System ist, kann ich auf die Probleme näher eingehen.
Alles async, nur längeren/cpu instensiven code mit run_in_executor:
Aber wegen der Probleme habe ich nun versucht mir zu überlegen, wie denn nun ein echter guter async Programmierer das System umsetzen würde und ob das vielleicht besser wäre. Vermutlich würde er, wie man es überall so liest, das ganze system auf async umstellen. Das heißt prinzipiell läuft erstmal alles im async loop. Für blockenden/cpu intensiven code gibt es dann die asyncio.run_in_executor Funktione, welche diesen code dann in Threads/Prozesse auslagert, um den event loop nicht zu blockieren. Nur besteht mein System aus ca. 90% (teils cpu intensivem) Code und 10% API call - code (grob geschätzt). Das heißt 90% meines codes würde dann durch run_in_executor durchgeführt, womit wir theoretisch doch wieder beim run_coroutine_threadsafe System sind. Es ist quasi dasselbe nur invertiert.
Nun kann man aber argumentieren, anstatt jede einzelne strategie==Bot in einem eigenen Prozess laufen zu lassen, wäre es möglicherweise schon effizienter nur für langen wirklich cpu intensiven code diese Prozess-Keule (übertragung zwischen prozessen bedeuted deepcopy und das bedeutet zeitverlust) rauszuholen und für die kürzeren code abschnitte threads zu nehmen. So wäre die Ressourcennutzung von dem speziell aufgerufenem code abhängig und nicht von dem skript und somit potentiell sinnvoller aufgeteilt, weshalb dieser Weg theoretisch also besser wäre, als "sync" code mit run_coroutine_threadsafe zu verwenden... ganz besonders wenn es zb um eine strategie geht, die zb nur stündlich aktiv werden soll (soll ja aber wie erwähnt jede strategie möglich sein)
Angenommen dieser Ansatz ist also der bessere: Wie sieht meine Struktur dann aus? Wenn ich im Strategieskript also eine Funktion habe, welche, um ein exaktes beispiel zu nennen, das platzieren einer order vorbereitet, den API call dafür macht, die antwort auswertet, dann einen weiteren api call macht um weitere infos über die order zu erlangen, die antwort auswertet und die order dann gegebenenfalls wieder cancelt (vorbereitung, api call und nachbereitung). Diese Operation gehört zusammen (eigene umsetzung eines fill_or_kill auftrags), es würde also zu massiver verwirrung und viel viel viel zuvielen Funktionen führen, wenn jeder codeabschnitt vor/nach den calls in eine eigene Funktion kommen würde, damit ich sie mit run_in_executor aufrufen könnte. Ich kann aber auch nicht einfach alles im eventloop durchführen, da dies bei vielen solcher anfragen ja skaliert und den event loop so sehr lange blockieren könnte.
Daher scheint mir die Version mit run_in_executor auch nicht wirklich sinnvoll, oder was übersehe ich?
Ganz abgesehen davon, dass ich nun nicht mehr wüsste, wie die unterschiedlichen Strategien nun aufgebaut (eigenes skript pro strategie, geht das noch ?) und laufen würden.
eventbasiert:
Natürlich kenne ich bereits ein paar "Backtesting" module. Diese laufen eventbasiert (was sich bei asyncio glaube ich anbietet), dh. in jedem Tick wird eine hauptfunktion im strategieskript aufgerufen und zb. die aktuellen Preisdaten übergeben. Das strategieskript kann damit dann machen was es will.
Sowas köööönnte man theoretisch auch für mein system verwenden, aber ich hatte bisher nicht das gefühl, dass das so einfach funktioniert. Wenn ich websocket verwende, dann bekomme ich extrem viele Preisdaten pro Sekunde, die kann ich unmöglich alle als Event weiterleiten.
Also bräuchte ich etwas, das zb stattdessen einmal pro Sekunde die aktuellen Preisdaten weiterleitet. Aber es soll ja für beliebige Strategien funktionieren. Sollten dann nur langsame Strategien angeschlossen sein, wäre es zuviel des Guten. Sollte eine schnellere Strategie angeschlossen sein, wäre es zu wenig.
Daher dachte ich, es wäre besser, wenn jeder Bot selbst seinen Rythmus bestimmt und aktiv nach den aktuellen Preisdaten fragt, wenn er sie braucht. Oder widerspricht sich das alles nicht und kann dennoch umgesetzt werden?
Ich möchte euch bitten nur zu antworten, wenn ihr direkt auf das beschriebene System "was soll es tun" eingeht und wirklich Lösungsvorschläge dazu habt.
Gerne können wir das ganze auch in einem Voicechat besprechen und wenn gewünscht zahle ich auch für diese "Unterrichtsstunde".
Aber ich brauche hier wirklich auf meinen speziellen Fall abgepasste Antworten. Allgemeine Antworten oder "lies dir xy durch" helfen hier sicherlich nicht.
Das von dir beschriebene System ist viel zu komplex als dass man tatsächlich realistisch schätzen könnte. Mal abgesehen davon dass dies variieren wird und stark von der Performance der API (aus Client Sicht, da kommt ja dann noch Netzwerk zwischen dir und dem Server hinzu) abhängt.Nur besteht mein System aus ca. 90% (teils cpu intensivem) Code und 10% API call - code (grob geschätzt).
Hast du Telemetrie in irgendeiner Form oder mal profiling betrieben um zu sehen wo es konkret hängt, falls es überhaupt irgendwo hängt? Falls du es noch nicht getan hast, würde ich damit anfangen. Alles andere ist Zeitverschwendung.
Wenn dein System gar nicht soweit steht dass du sowas ermitteln könntest, bau es erstmal und halte es so simpel wie möglich bevor du Zeit an der falschen Stelle investierst.
Übrigens Trading Systeme wie sie im Finanzmarkt genutzt werden sind ein sehr spezielles Feld für sich und erfordern einiges an speziellen know how an dass man wahrscheinlich auch nicht so schnell kommt wenn man nicht in genau dem Umfeld arbeitet. Selbst mit dem Wissen ist es wahrscheinlich unrealistisch sowas als Hobby Projekt zu betreiben.