Mit welchen Modulen werden Multithread-Anwendungen in Python geschrieben? Ich bin mir der grundlegenden Parallelitätsmechanismen bewusst, die von der Sprache und auch von Stackless Python bereitgestellt werden , aber was sind ihre jeweiligen Stärken und Schwächen?
76
asyncio
bitte mit beantworten ? Vielen Dank im Voraus und würde gerne sehen, wie der Code aussehen würde.Antworten:
In der Reihenfolge zunehmender Komplexität:
Verwenden Sie das Einfädelmodul
Vorteile:
Nachteile:
Verwenden Sie das Multiprozessor- Modul
Im einfachen Anwendungsfall sieht dies genauso aus wie mit,
threading
außer dass jede Aufgabe in einem eigenen Prozess ausgeführt wird, nicht in einem eigenen Thread. (Fast wörtlich: Wenn Sie nehmen Elis Beispiel , und ersetzenthreading
mitmultiprocessing
,Thread
mitProcess
, undQueue
(das Modul) mitmultiprocessing.Queue
, sollte es ganz gut laufen.)Vorteile:
Nachteile:
Verwenden Sie ein Ereignismodell wie Twisted
Vorteile:
Nachteile:
In allen Fällen gehe ich davon aus, dass Sie bereits viele Probleme im Zusammenhang mit Multitasking verstanden haben, insbesondere die schwierige Frage, wie Daten zwischen Aufgaben ausgetauscht werden können. Wenn Sie aus irgendeinem Grund nicht wissen, wann und wie Sie Sperren und Bedingungen verwenden sollen, müssen Sie mit diesen beginnen. Multitasking-Code ist voller Feinheiten und Fallstricke, und es ist wirklich am besten, ein gutes Verständnis der Konzepte zu haben, bevor Sie beginnen.
quelle
multiprocessing
langsamer. In diesem Fall kann jedoch auch Ihre Multithreading-Nutzung erheblich verbessert werden.Sie haben bereits eine ganze Reihe von Antworten erhalten, von "gefälschten Threads" bis hin zu externen Frameworks, aber ich habe niemanden erwähnt
Queue.Queue
- die "geheime Sauce" des CPython-Threadings.Zum Erweitern: Solange Sie keine reine Python-CPU-lastige Verarbeitung überlappen müssen (in diesem Fall benötigen Sie dies
multiprocessing
- aber es kommt auch mit einer eigenenQueue
Implementierung, sodass Sie mit einigen erforderlichen Vorsichtsmaßnahmen den allgemeinen Rat I anwenden können Ich gebe ;-), Pythons eingebautesthreading
wird es tun ... aber es wird es viel besser machen, wenn Sie es mit Bedacht verwenden , z. B. wie folgt.Shared Memory "vergessen", angeblich das Hauptvorteil von Threading und Multiprocessing - es funktioniert nicht gut, es skaliert nicht gut, hat es nie, wird es nie. Verwenden Sie den gemeinsam genutzten Speicher nur für Datenstrukturen, die einmal eingerichtet wurden, bevor Sie Sub-Threads erzeugen, und die danach nie mehr geändert wurden. Machen Sie für alles andere einen einzelnen Thread für diese Ressource verantwortlich und kommunizieren Sie mit diesem Thread über
Queue
.Weisen Sie jeder Ressource, die Sie normalerweise durch Sperren schützen möchten, einen speziellen Thread zu: eine veränderbare Datenstruktur oder eine zusammenhängende Gruppe davon, eine Verbindung zu einem externen Prozess (einer Datenbank, einem XMLRPC-Server usw.), einer externen Datei usw. Richten Sie einen kleinen Thread-Pool für allgemeine Aufgaben ein, für die keine dedizierte Ressource dieser Art vorhanden ist oder benötigt wird. Erstellen Sie keine Threads nach Bedarf, da Sie sonst durch den Overhead beim Wechseln der Threads überfordert werden.
Die Kommunikation zwischen zwei Threads erfolgt immer über
Queue.Queue
- eine Form der Nachrichtenübermittlung, die einzige vernünftige Grundlage für die Mehrfachverarbeitung (neben dem vielversprechenden Transaktionsspeicher, für den ich jedoch keine produktionswürdigen Implementierungen außer In Haskell kenne).Jeder dedizierte Thread, der eine einzelne Ressource (oder einen kleinen zusammenhängenden Satz von Ressourcen) verwaltet, wartet auf Anforderungen in einer bestimmten Queue.Queue-Instanz. Threads in einem Pool warten auf eine einzelne gemeinsam genutzte Queue.Queue (Queue ist solide threadsicher und wird Sie dabei nicht scheitern lassen).
Threads, die nur eine Anforderung in einer Warteschlange (gemeinsam genutzt oder dediziert) in die Warteschlange stellen müssen, tun dies, ohne auf Ergebnisse zu warten, und fahren fort. Threads, die möglicherweise ein Ergebnis oder eine Bestätigung für eine Anforderungswarteschlange benötigen, erhalten ein Paar (Anforderung, Empfangswarteschlange) mit einer Instanz von Queue.Queue, die sie gerade erstellt haben, und schließlich, wenn die Antwort oder Bestätigung für das Fortfahren unabdingbar ist, erhalten sie (Warten) ) aus ihrer Empfangswarteschlange. Stellen Sie sicher, dass Sie bereit sind, sowohl Fehlerantworten als auch echte Antworten oder Bestätigungen zu erhalten (Twisted's
deferred
sind hervorragend darin, diese Art von strukturierter Antwort zu organisieren, übrigens!).Sie können Queue auch verwenden, um Instanzen von Ressourcen zu "parken", die von einem beliebigen Thread verwendet werden können, jedoch niemals von mehreren Threads gleichzeitig gemeinsam genutzt werden (DB-Verbindungen mit einigen DBAPI-Komponenten, Cursor mit anderen usw.). Auf diese Weise können Sie sich entspannen Die Anforderung eines dedizierten Threads zugunsten von mehr Pooling (ein Pool-Thread, der aus der gemeinsam genutzten Warteschlange eine Anforderung erhält, die eine in der Warteschlange befindliche Ressource benötigt, erhält diese Ressource aus der entsprechenden Warteschlange und wartet bei Bedarf usw. usw.).
Twisted ist eigentlich eine gute Möglichkeit, dieses Menuett (oder den Square Dance) zu organisieren, nicht nur dank verzögerter, sondern auch aufgrund seiner soliden, soliden, hoch skalierbaren Basisarchitektur: Sie können Dinge so arrangieren, dass Threads oder Unterprozesse nur dann verwendet werden, wenn wirklich gerechtfertigt, während die meisten Dinge, die normalerweise als threadwürdig angesehen werden, in einem einzigen ereignisgesteuerten Thread ausgeführt werden.
Aber mir ist klar, dass Twisted nicht jedermanns Sache ist - der Ansatz "Ressourcen widmen oder bündeln, Warteschlange im Wazoo verwenden, niemals etwas tun, das eine Sperre benötigt, oder, wie Guido verbietet, ein noch weiter fortgeschrittenes Synchronisationsverfahren wie Semaphor oder Bedingung" kann Wird auch dann verwendet, wenn Sie sich nicht mit asynchronen ereignisgesteuerten Methoden auseinandersetzen können, und bietet dennoch mehr Zuverlässigkeit und Leistung als jeder andere weit verbreitete Threading-Ansatz, auf den ich jemals gestoßen bin.
quelle
Es hängt davon ab, was Sie versuchen, aber ich bin teilweise daran interessiert, nur das
threading
Modul in der Standardbibliothek zu verwenden, da es wirklich einfach ist, jede Funktion zu übernehmen und sie einfach in einem separaten Thread auszuführen.from threading import Thread def f(): ... def g(arg1, arg2, arg3=None): .... Thread(target=f).start() Thread(target=g, args=[5, 6], kwargs={"arg3": 12}).start()
Und so weiter. Ich habe oft ein Producer / Consumer-Setup mit einer synchronisierten Warteschlange, die vom
Queue
Modul bereitgestellt wirdfrom Queue import Queue from threading import Thread q = Queue() def consumer(): while True: print sum(q.get()) def producer(data_source): for line in data_source: q.put( map(int, line.split()) ) Thread(target=producer, args=[SOME_INPUT_FILE_OR_SOMETHING]).start() for i in range(10): Thread(target=consumer).start()
quelle
Kamaelia ist ein Python-Framework zum Erstellen von Anwendungen mit vielen Kommunikationsprozessen.
Hier ist ein Video von Pycon 2009. Zunächst wird Kamaelia mit Twisted und Parallel Python verglichen und anschließend Kamaelia demonstriert.Einfache Parallelität mit Kamaelia - Teil 1 (59:08)
Einfache Parallelität mit Kamaelia - Teil 2 (18:15)
quelle
In Bezug auf Kamaelia deckt die obige Antwort den Nutzen hier nicht wirklich ab. Kamaelias Ansatz bietet eine einheitliche Schnittstelle, die pragmatisch und nicht perfekt ist, um Threads, Generatoren und Prozesse in einem einzigen System für die Parallelität zu behandeln.
Grundsätzlich bietet es eine Metapher für ein laufendes Objekt mit Posteingängen und Postausgängen. Sie senden Nachrichten an Postausgänge, und wenn sie miteinander verbunden sind, fließen Nachrichten von Postausgängen zu Posteingängen. Diese Metapher / API bleibt gleich, unabhängig davon, ob Sie Generatoren, Threads oder Prozesse verwenden oder mit anderen Systemen sprechen.
Der "nicht perfekte" Teil ist darauf zurückzuführen, dass syntaktischer Zucker für Posteingänge und Postausgänge noch nicht hinzugefügt wurde (obwohl dies diskutiert wird) - es liegt ein Schwerpunkt auf Sicherheit / Benutzerfreundlichkeit im System.
In Kamaelia wird dies anhand des obigen Beispiels für Hersteller und Verbraucher unter Verwendung von Bare-Threading wie folgt:
In diesem Beispiel spielt es keine Rolle, ob es sich um Komponenten mit Gewinde oder auf andere Weise handelt. Der einzige Unterschied besteht aus der Verwendungsperspektive in der Basisklasse für die Komponente. Generatorkomponenten kommunizieren über Listen, Thread-Komponenten über Queue.Queues und prozessbasiert über os.pipes.
Der Grund für diesen Ansatz besteht jedoch darin, das Debuggen von Fehlern zu erschweren. Beim Threading - oder bei jeder Parallelität des gemeinsam genutzten Speichers - besteht das Hauptproblem darin, dass versehentlich Aktualisierungen der gemeinsam genutzten Daten unterbrochen werden. Durch die Verwendung der Nachrichtenübermittlung eliminieren Sie eine Klasse von Fehlern.
Wenn Sie überall nacktes Threading und Sperren verwenden, gehen Sie im Allgemeinen davon aus, dass Sie beim Schreiben von Code keine Fehler machen. Während wir alle danach streben, ist es sehr selten, dass dies passieren wird. Indem Sie das Sperrverhalten an einer Stelle zusammenfassen, vereinfachen Sie, wo etwas schief gehen kann. (Kontexthandler helfen, aber nicht bei versehentlichen Aktualisierungen außerhalb des Kontexthandlers.)
Offensichtlich kann nicht jeder Code als Nachrichtenübermittlung und gemeinsamer Stil geschrieben werden, weshalb Kamaelia auch über einen einfachen Software-Transaktionsspeicher (STM) verfügt, was eine wirklich nette Idee mit einem bösen Namen ist - es ist eher eine Versionskontrolle für Variablen - dh Überprüfen Sie einige Variablen, aktualisieren Sie sie und legen Sie sie fest. Wenn Sie einen Konflikt bekommen, spülen Sie und wiederholen.
Relevante Links:
Wie auch immer, ich hoffe das ist eine nützliche Antwort. FWIW, der Hauptgrund für Kamaelias Setup ist, die Parallelität sicherer und einfacher in Python-Systemen zu verwenden, ohne dass der Schwanz mit dem Hund wedelt. (dh der große Eimer mit Komponenten
Ich kann verstehen, warum die andere Kamaelia-Antwort modifiziert wurde, da sie selbst für mich eher wie eine Anzeige als wie eine Antwort aussieht. Als Autor von Kamaelia ist es schön, Begeisterung zu sehen, obwohl ich hoffe, dass dies etwas relevantere Inhalte enthält :-)
Und das ist meine Art zu sagen, bitte nehmen Sie den Vorbehalt, dass diese Antwort per Definition voreingenommen ist, aber für mich ist es Kamaelias Ziel, zu versuchen, die IMO-Best Practice zu verpacken. Ich würde vorschlagen, ein paar Systeme auszuprobieren und zu sehen, welches für Sie funktioniert. (auch wenn dies für einen Stapelüberlauf ungeeignet ist, sorry - ich bin neu in diesem Forum :-)
quelle
Ich würde die Microthreads (Tasklets) von Stackless Python verwenden, wenn ich überhaupt Threads verwenden müsste.
Ein ganzes Online-Spiel (Massivly Multiplayer) basiert auf Stackless und seinem Multithreading-Prinzip - da das Original für die Massivly Multiplayer-Eigenschaft des Spiels nur zu langsam ist.
Von Threads in CPython wird dringend abgeraten. Ein Grund ist die GIL - eine globale Interpretersperre -, die das Threading für viele Teile der Ausführung serialisiert. Meine Erfahrung ist, dass es wirklich schwierig ist, auf diese Weise schnelle Anwendungen zu erstellen. Meine Beispielcodierungen waren beim Threading langsamer - mit einem Kern (aber viele Wartezeiten auf die Eingabe hätten einige Leistungssteigerungen ermöglichen sollen).
Verwenden Sie bei CPython nach Möglichkeit lieber separate Prozesse.
quelle
Wenn Sie sich wirklich die Hände schmutzig machen möchten, können Sie versuchen, mit Generatoren Coroutinen zu fälschen . Es ist wahrscheinlich nicht das effizienteste in Bezug auf die Arbeit, aber Coroutinen bieten Ihnen eine sehr feine Kontrolle über die Genossenschaft Multitasking und nicht über präventives Multitasking, das Sie anderswo finden.
Ein Vorteil, den Sie feststellen werden, ist, dass Sie im Großen und Ganzen keine Sperren oder Mutexe benötigen, wenn Sie kooperatives Multitasking verwenden. Der wichtigere Vorteil für mich war jedoch die Umschaltgeschwindigkeit zwischen "Threads" von nahezu Null. Natürlich soll Stackless Python auch dafür sehr gut sein; und dann gibt es Erlang, wenn es nicht hat Python sein.
Der wahrscheinlich größte Nachteil beim kooperativen Multitasking ist das generelle Fehlen einer Problemumgehung zum Blockieren von E / A. Und in den gefälschten Coroutinen tritt auch das Problem auf, dass Sie "Threads" nur von der obersten Ebene des Stapels innerhalb eines Threads wechseln können.
Nachdem Sie eine noch etwas komplexere Anwendung mit gefälschten Coroutinen erstellt haben, werden Sie die Arbeit, die mit der Prozessplanung auf Betriebssystemebene verbunden ist, wirklich zu schätzen wissen.
quelle