Für mich ist das eigentlich ganz einfach:
Die Unterprozessoption :
subprocess
dient 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 subprocess
synchron 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 subprocess
Moduls 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, fcntl
Ihre Popen
PIPE-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 signal
Modul und einfache UNIX / Linux-Signale der alten Schule verwenden können - und Ihre Prozesse sozusagen sanft beenden.
Die Mehrfachverarbeitungsoption :
multiprocessing
dient 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 multiprocessing
IPC nach Queue
Möglichkeit um die Objekte des Moduls herum zu erstellen. Sie können jedoch auch Event
Objekte und verschiedene andere Funktionen verwenden (von denen einige vermutlich mmap
auf der Unterstützung auf Plattformen basieren, auf denen diese Unterstützung ausreicht).
Das Python- multiprocessing
Modul 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:
threading
ist 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).
threading
leidet 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 multiprocessing
gibt 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
threading
mit 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.