Entscheiden Sie sich in Python für Unterprozess, Multiprocessing und Thread?

110

Ich möchte mein Python-Programm parallelisieren, damit es mehrere Prozessoren auf dem Computer verwenden kann, auf dem es ausgeführt wird. Meine Parallelisierung ist sehr einfach, da alle parallelen "Threads" des Programms unabhängig sind und ihre Ausgabe in separate Dateien schreiben. Ich brauche die Threads nicht, um Informationen auszutauschen, aber ich muss unbedingt wissen, wann die Threads fertig sind, da einige Schritte meiner Pipeline von ihrer Ausgabe abhängen.

Portabilität ist wichtig, da ich möchte, dass dies auf jeder Python-Version unter Mac, Linux und Windows ausgeführt wird. Welches Python-Modul ist angesichts dieser Einschränkungen am besten geeignet, um dies zu implementieren? Ich versuche, mich zwischen Thread, Unterprozess und Multiprocessing zu entscheiden, die alle verwandte Funktionen bieten.

Irgendwelche Gedanken dazu? Ich möchte die einfachste Lösung, die portabel ist.

Vaibhav Mule
quelle
Verwandte: stackoverflow.com/questions/1743293/… (lesen Sie meine Antwort dort, um zu sehen, warum Threads kein Starter für reinen Python-Code sind)
1
"Jede Python-Version" ist viel zu vage. Python 2.3? 1.x? 3.x? Es ist einfach eine unmögliche Bedingung zu erfüllen.
Detly

Antworten:

64

multiprocessingist ein großartiges Modul für Schweizer Taschenmesser. Es ist allgemeiner als Threads, da Sie sogar Remote-Berechnungen durchführen können. Dies ist daher das Modul, das ich Ihnen empfehlen würde.

Mit dem subprocessModul können Sie auch mehrere Prozesse starten, aber ich fand es weniger bequem als das neue Multiprozessor-Modul.

Threads sind notorisch subtil, und mit CPython sind Sie häufig auf einen Kern beschränkt (obwohl, wie in einem der Kommentare erwähnt, die globale Interpreter-Sperre (GIL) in C-Code freigegeben werden kann, der aus Python-Code aufgerufen wird). .

Ich glaube, dass die meisten Funktionen der drei von Ihnen genannten Module plattformunabhängig genutzt werden können. Beachten Sie auf der Portabilitätsseite, dass dies multiprocessingerst seit Python 2.6 Standard ist (eine Version für einige ältere Versionen von Python gibt es jedoch). Aber es ist ein großartiges Modul!

Eric O Lebigot
quelle
1
Für eine Aufgabe habe ich nur das "Multiprocessing" -Modul und seine pool.map () -Methode verwendet. Stück Kuchen !
kmonsoor
Wird auch etwas wie Sellerie in Betracht gezogen? Warum ist es oder nicht?
user3245268
Soweit ich das beurteilen kann, ist Sellerie stärker involviert (Sie müssen einen Nachrichtenbroker installieren), aber es ist eine Option, die je nach Problem wahrscheinlich in Betracht gezogen werden sollte.
Eric O Lebigot
186

Für mich ist das eigentlich ganz einfach:

Die Unterprozessoption :

subprocessdient zum Ausführen anderer ausführbarer Dateien --- es handelt sich im Grunde genommen um einen Wrapper os.fork()und os.execve()mit Unterstützung für optionale Installationen (Einrichten von PIPEs zu und von den Unterprozessen. Natürlich können Sie auch andere IPC-Mechanismen (Inter-Process Communications) wie Sockets oder Posix oder verwenden Gemeinsamer SysV-Speicher. Sie sind jedoch auf die Schnittstellen und IPC-Kanäle beschränkt, die von den von Ihnen aufgerufenen Programmen unterstützt werden.

In der Regel wird jedes subprocesssynchron verwendet - einfach ein externes Dienstprogramm aufrufen und dessen Ausgabe zurücklesen oder auf dessen Fertigstellung warten (möglicherweise die Ergebnisse aus einer temporären Datei lesen oder nachdem sie in einer Datenbank veröffentlicht wurden).

Man kann jedoch Hunderte von Unterprozessen erzeugen und abfragen. Mein persönlicher Lieblings-Utility- Classh macht genau das. Der größte Nachteil des subprocessModuls besteht darin, dass die E / A-Unterstützung im Allgemeinen blockiert. Es gibt einen Entwurf für PEP-3145 , um dies in einer zukünftigen Version von Python 3.x zu beheben, und einen alternativen Asyncproc (Warnung, die direkt zum Download führt, nicht zu irgendeiner Art von Dokumentation oder README). Ich habe auch festgestellt, dass es relativ einfach ist, fcntlIhre PopenPIPE-Dateideskriptoren direkt zu importieren und zu bearbeiten - obwohl ich nicht weiß, ob dies auf Nicht-UNIX-Plattformen portierbar ist.

(Update: 7. August 2019: Python 3-Unterstützung für Ayncio-Unterprozesse: Asyncio-Unterprozesse )

subprocess hat fast keine Unterstützung für die Ereignisbehandlung ... obwohl Sie das signalModul und einfache UNIX / Linux-Signale der alten Schule verwenden können - und Ihre Prozesse sozusagen sanft beenden.

Die Mehrfachverarbeitungsoption :

multiprocessingdient zum Ausführen von Funktionen in Ihrem vorhandenen (Python-) Code mit Unterstützung für eine flexiblere Kommunikation zwischen dieser Prozessfamilie. Insbesondere ist es am besten, Ihren multiprocessingIPC nach QueueMöglichkeit um die Objekte des Moduls herum zu erstellen. Sie können jedoch auch EventObjekte und verschiedene andere Funktionen verwenden (von denen einige vermutlich mmapauf der Unterstützung auf Plattformen basieren, auf denen diese Unterstützung ausreicht).

Das Python- multiprocessingModul soll Schnittstellen und Funktionen bereitstellen , threading die CPython sehr ähnlich sind, während CPython Ihre Verarbeitung trotz GIL (Global Interpreter Lock) auf mehrere CPUs / Kerne skalieren kann. Es nutzt alle fein abgestimmten SMP-Sperr- und Kohärenzanstrengungen, die von Entwicklern Ihres Betriebssystemkerns durchgeführt wurden.

Die Threading- Option:

threadingist für einen relativ engen Bereich von Anwendungen gedacht, die E / A-gebunden sind (nicht über mehrere CPU-Kerne skaliert werden müssen) und die von der extrem geringen Latenz und dem Switching-Overhead beim Thread-Switching (mit gemeinsam genutztem Kernspeicher) im Vergleich zum Prozess / profitieren. Kontextwechsel. Unter Linux ist dies fast die leere Menge (Linux-Prozesswechselzeiten liegen extrem nahe an den Thread-Schaltern).

threadingleidet an zwei großen Nachteilen in Python .

Eine davon ist natürlich implementierungsspezifisch - betrifft hauptsächlich CPython. Das ist die GIL. Zum größten Teil profitieren die meisten CPython-Programme nicht von der Verfügbarkeit von mehr als zwei CPUs (Kernen), und häufig leidet die Leistung unter dem GIL-Sperrkonflikt.

Das größere Problem, das nicht implementierungsspezifisch ist, besteht darin, dass Threads denselben Speicher, dieselben Signalhandler, Dateideskriptoren und bestimmte andere Betriebssystemressourcen verwenden. Daher muss der Programmierer äußerst vorsichtig sein, wenn Objekte gesperrt, Ausnahmen behandelt und andere Aspekte ihres Codes behandelt werden, die sowohl subtil sind als auch den gesamten Prozess (eine Reihe von Threads) beenden, blockieren oder blockieren können.

Im Vergleich dazu multiprocessinggibt das Modell jedem Prozess seinen eigenen Speicher, Dateideskriptoren usw. Ein Absturz oder eine nicht behandelte Ausnahme in einem von ihnen tötet nur diese Ressource, und die robuste Behandlung des Verschwindens eines untergeordneten Prozesses oder eines Geschwisterprozesses kann erheblich einfacher sein als das Debuggen und Isolieren und Beheben oder Umgehen ähnlicher Probleme in Threads.

  • (Hinweis: Die Verwendung threadingmit wichtigen Python-Systemen wie NumPy kann erheblich weniger unter GIL-Konflikten leiden als die meisten Ihrer eigenen Python-Codes. Dies liegt daran, dass sie speziell dafür entwickelt wurden. Die nativen / binären Teile von NumPy, Gibt beispielsweise die GIL frei, wenn dies sicher ist.

Die verdrehte Option:

Es ist auch erwähnenswert, dass Twisted eine weitere Alternative bietet, die sowohl elegant als auch sehr schwierig zu verstehen ist . Grundsätzlich bietet Twisted ein ereignisgesteuertes kooperatives Multitasking innerhalb eines (einzelnen) Prozesses, da die Gefahr besteht, dass es zu stark vereinfacht wird, bis Fans von Twisted mein Zuhause mit Heugabeln und Fackeln stürmen.

Um zu verstehen, wie dies möglich ist, sollte man sich über die Funktionen von select()(die auf select () oder poll () oder ähnlichen Betriebssystemaufrufen basieren können) informieren. Grundsätzlich hängt alles von der Möglichkeit ab, eine Anforderung an das Betriebssystem zu stellen, bis eine Aktivität in einer Liste von Dateideskriptoren oder eine Zeitüberschreitung vorliegt.

Das Erwachen aus jedem dieser Aufrufe von select()ist ein Ereignis - entweder ein Ereignis, bei dem Eingaben für eine bestimmte Anzahl von Sockets oder Dateideskriptoren verfügbar (lesbar) sind, oder Pufferplatz für andere (beschreibbare) Deskriptoren oder Sockets verfügbar wird, einige außergewöhnliche Bedingungen (TCP) Out-of-Band-PUSH-Pakete (z. B.) oder ein TIMEOUT.

Daher basiert das Twisted-Programmiermodell darauf, diese Ereignisse zu behandeln und dann den resultierenden "Haupt" -Handler zu durchlaufen, sodass er die Ereignisse an Ihre Handler senden kann.

Ich persönlich denke an den Namen Twisted als Anspielung auf das Programmiermodell ... da Ihre Herangehensweise an das Problem in gewissem Sinne von innen nach außen "verdreht" sein muss. Anstatt Ihr Programm als eine Reihe von Operationen für Eingabedaten und Ausgaben oder Ergebnisse zu verstehen, schreiben Sie Ihr Programm als Dienst oder Dämon und definieren, wie es auf verschiedene Ereignisse reagiert. (Tatsächlich ist die Kern- "Hauptschleife" eines Twisted-Programms (normalerweise? Immer?) A reactor()).

Die größten Herausforderungen bei der Verwendung von Twisted bestehen darin, sich mit dem ereignisgesteuerten Modell auseinanderzusetzen und die Verwendung von Klassenbibliotheken oder Toolkits zu vermeiden, die nicht für die Zusammenarbeit innerhalb des Twisted-Frameworks geschrieben wurden. Aus diesem Grund bietet Twisted eigene Module für die Verarbeitung von SSH-Protokollen, für Flüche und eigene Unterprozess- / Popen-Funktionen sowie viele andere Module und Protokollhandler an, die auf den ersten Blick die Dinge in den Python-Standardbibliotheken zu duplizieren scheinen.

Ich denke, es ist nützlich, Twisted auf konzeptioneller Ebene zu verstehen, auch wenn Sie nie beabsichtigen, es zu verwenden. Es kann Einblicke in die Leistung, die Konkurrenz und die Ereignisbehandlung in Ihrem Threading, in der Mehrfachverarbeitung und sogar in der Verarbeitung von Unterprozessen sowie in jede verteilte Verarbeitung geben, die Sie durchführen.

( Hinweis: Neuere Versionen von Python 3.x enthalten Asyncio-Funktionen (asynchrone E / A) wie z async def , den @ async.coroutine- Dekorator und das Schlüsselwort await und ergeben sich aus der zukünftigen Unterstützung. Alle diese Funktionen ähneln in etwa denen Aus Prozessperspektive (kooperatives Multitasking) verdreht ). (Den aktuellen Status der Twisted-Unterstützung für Python 3 finden Sie unter: https://twistedmatrix.com/documents/current/core/howto/python3.html )

Die verteilte Option:

Ein weiterer Bereich der Verarbeitung, nach dem Sie nicht gefragt haben, der jedoch eine Überlegung wert ist, ist der der verteilten Verarbeitung. Es gibt viele Python-Tools und Frameworks für die verteilte Verarbeitung und parallele Berechnung. Persönlich denke ich, dass die am einfachsten zu verwendende eine ist, die am seltensten als in diesem Raum befindlich angesehen wird.

Es ist fast trivial, eine verteilte Verarbeitung um Redis herum aufzubauen . Der gesamte Schlüsselspeicher kann zum Speichern von Arbeitseinheiten und Ergebnissen verwendet werden, Redis-LISTs können als gleiches Queue()Objekt verwendet werden und die PUB / SUB-Unterstützung kann für eine Eventähnliche Behandlung verwendet werden. Sie können Ihre Schlüssel hashen und Werte verwenden, die über einen losen Cluster von Redis-Instanzen repliziert wurden, um die Topologie und Hash-Token-Zuordnungen zu speichern, um konsistentes Hashing und Failover für die Skalierung bereitzustellen, die über die Kapazität einer einzelnen Instanz zur Koordinierung Ihrer Mitarbeiter hinausgeht und Marshalling-Daten (eingelegt, JSON, BSON oder YAML) unter ihnen.

Wenn Sie mit dem Aufbau einer größeren und komplexeren Lösung für Redis beginnen, implementieren Sie natürlich viele der Funktionen, die bereits mit Celery , Apache Spark und Hadoop gelöst wurden. Zoowärter , ETCD , Cassandra und so weiter. Diese haben alle Module für den Python-Zugriff auf ihre Dienste.

[Update: Einige Ressourcen sollten berücksichtigt werden, wenn Sie Python für rechenintensiv auf verteilten Systemen in Betracht ziehen: IPython Parallel und PySpark . Während dies verteilte Allzweck-Computersysteme sind, sind sie besonders zugängliche und beliebte Subsysteme (Data Science and Analytics).

Fazit

Dort haben Sie die Möglichkeit, Alternativen für Python zu verarbeiten, von Single-Threaded mit einfachen synchronen Aufrufen zu Subprozessen, Pools abgefragter Subprozesse, Threaded- und Multiprocessing, ereignisgesteuertem kooperativem Multitasking bis hin zur verteilten Verarbeitung.

Jim Dennis
quelle
1
Es ist jedoch schwierig, Multiprocessing mit Klassen / OOP zu verwenden.
Tjorriemorrie
2
@Tjorriemorrie: Ich gehe davon aus, dass es schwierig ist, Methodenaufrufe an Instanzen von Objekten zu senden, die sich möglicherweise in anderen Prozessen befinden. Ich würde vorschlagen, dass dies das gleiche Problem ist, das Sie mit Threads haben würden, aber leichter sichtbar sind (anstatt zerbrechlich zu sein und obskuren Rennbedingungen zu unterliegen). Ich würde denken, dass der empfohlene Ansatz darin besteht, dafür zu sorgen, dass alle derartigen Versendungen über Warteschlangenobjekte erfolgen, die Single-Threaded, Multi-Threaded und prozessübergreifend funktionieren. (Mit einigen Redis oder Celery Queue Implementierung, sogar über einen Cluster von Knoten)
Jim Dennis
2
Das ist eine wirklich gute Antwort. Ich wünschte, es wäre in der Einführung in die Parallelität in den Python3-Dokumenten.
root-11
1
@ root-11 Sie können es gerne den Dokumentenverwaltern vorschlagen. Ich habe es hier zur kostenlosen Nutzung veröffentlicht. Sie und sie können es gerne ganz oder teilweise verwenden.
Jim Dennis
"Für mich ist das eigentlich ganz einfach:" Ich liebe es.
Vielen
5

In einem ähnlichen Fall entschied ich mich für separate Prozesse und die wenig notwendige Kommunikation über den Netzwerk-Socket. Es ist sehr portabel und recht einfach mit Python zu tun, aber wahrscheinlich nicht einfacher (in meinem Fall hatte ich auch eine andere Einschränkung: die Kommunikation mit anderen in C ++ geschriebenen Prozessen).

In Ihrem Fall würde ich mich wahrscheinlich für Multiprozess entscheiden, da Python-Threads, zumindest bei Verwendung von CPython, keine echten Threads sind. Nun, es handelt sich um native Systemthreads, aber von Python aufgerufene C-Module können die GIL freigeben oder nicht und anderen Threads ermöglichen, dass sie beim Aufrufen von Blockierungscode ausgeführt werden.

kriss
quelle
4

Um mehrere Prozessoren in CPython zu verwenden, haben Sie nur das multiprocessingModul zur Auswahl . CPython sperrt seine Interna (die GIL ), wodurch verhindert wird, dass Threads auf anderen CPUs parallel arbeiten. Das multiprocessingModul erstellt neue Prozesse (wie subprocess) und verwaltet die Kommunikation zwischen ihnen.

Jochen Ritzel
quelle
5
Das ist nicht ganz richtig, AFAIK Sie können die GIL mithilfe der C-API freigeben, und es gibt andere Implementierungen von Python wie IronPython oder Jython, die nicht unter solchen Einschränkungen leiden. Ich habe allerdings nicht abgelehnt.
Bastien Léonard
1

Shell out und lassen Sie das Unix raus, um Ihre Arbeit zu erledigen:

Verwenden Sie Iterpipes , um den Unterprozess zu verpacken, und dann:

Von Ted Ziubas Seite

INPUTS_FROM_YOU | xargs -n1 -0 -P NUM ./process #NUM parallele Prozesse

ODER

Gnu Parallel wird auch dienen

Sie hängen mit GIL ab, während Sie die Jungs im Hinterzimmer losschicken, um Ihre Multicore-Arbeit zu erledigen.

kichernd
quelle
6
"Portabilität ist wichtig, da ich möchte, dass dies auf jeder Python-Version unter Mac, Linux und Windows ausgeführt wird."
Detly
Können Sie mit dieser Lösung wiederholt mit dem Job interagieren? Sie können dies in Multiprozessen tun, aber ich denke nicht in Unterprozessen.
Abalter