Wie funktionieren Threads in Python und was sind häufige Fallstricke beim Python-Threading?

84

Ich habe versucht, mich mit der Funktionsweise von Threads in Python zu beschäftigen, und es ist schwierig, gute Informationen über deren Funktionsweise zu finden. Ich vermisse vielleicht nur einen Link oder so, aber es scheint, dass die offizielle Dokumentation zu diesem Thema nicht sehr gründlich ist und ich keine gute Zusammenfassung finden konnte.

Soweit ich weiß, kann nur ein Thread gleichzeitig ausgeführt werden, und der aktive Thread wechselt alle 10 Anweisungen oder so?

Wo gibt es eine gute Erklärung oder können Sie eine liefern? Es wäre auch sehr schön, sich der häufigen Probleme bewusst zu sein, auf die Sie bei der Verwendung von Threads mit Python stoßen.

jdd
quelle

Antworten:

51

Ja, aufgrund der globalen Interpreter-Sperre (GIL) kann jeweils nur ein Thread ausgeführt werden. Hier sind einige Links mit einigen Einsichten dazu:

Vom letzten Link ein interessantes Zitat:

Lassen Sie mich erklären, was das alles bedeutet. Threads werden in derselben virtuellen Maschine und daher auf derselben physischen Maschine ausgeführt. Prozesse können auf derselben physischen Maschine oder auf einer anderen physischen Maschine ausgeführt werden. Wenn Sie Ihre Anwendung anhand von Threads erstellen, haben Sie nichts unternommen, um auf mehrere Computer zuzugreifen. Sie können also auf so viele Kerne skalieren, wie sich auf einem Computer befinden (was im Laufe der Zeit einige sein wird). Um jedoch wirklich Web-Skalen zu erreichen, müssen Sie das Problem mit mehreren Computern trotzdem lösen.

Wenn Sie Multi-Core verwenden möchten, definiert Pyprocessing eine prozessbasierte API für eine echte Parallelisierung. Das PEP enthält auch einige interessante Benchmarks.

Peter Hoffmann
quelle
1
Wirklich ein Kommentar zum Smoothspan-Zitat: Sicherlich beschränkt Sie Python-Threading effektiv auf einen Kern, selbst wenn die Maschine mehrere hat? Multicore kann Vorteile bringen, da der nächste Thread ohne Kontextwechsel betriebsbereit sein kann, Ihre Python-Threads jedoch niemals> 1 Kern gleichzeitig verwenden können.
James Brady
2
Richtig, Python-Threads sind praktisch auf den einen Kern beschränkt, es sei denn, ein C-Modul interagiert gut mit der GIL und führt einen eigenen nativen Thread aus.
Arafangion
Tatsächlich machen mehrere Kerne Threads weniger effizient, da bei der Überprüfung, ob jeder Thread auf die GIL zugreifen kann, viel Abwanderung erforderlich ist. Selbst mit der neuen GIL ist die Leistung noch schlechter ... dabeaz.com/python/NewGIL.pdf
Basic
2
Bitte beachten Sie, dass GIL-Überlegungen nicht für alle Dolmetscher gelten. Soweit mir bekannt ist, funktionieren sowohl IronPython als auch Jython ohne GIL, sodass ihr Code die Multiprozessor-Hardware effektiver nutzen kann. Wie von Arafangion erwähnt, kann der CPython-Interpreter auch ordnungsgemäß mit mehreren Threads ausgeführt werden, wenn Code, der keinen Zugriff auf Python-Datenelemente benötigt, die Sperre aufhebt und sie vor der Rückkehr erneut abruft.
Holdenweb
Was bewirkt einen Kontextwechsel zwischen den Threads in Python? Basiert es auf Timer-Interrupts? Blockieren oder ein bestimmter Yield Call?
CMCDragonkai
36

Python ist eine ziemlich einfach zu fädelnde Sprache, aber es gibt einige Einschränkungen. Das Wichtigste, was Sie wissen müssen, ist das Global Interpreter Lock. Dadurch kann nur ein Thread auf den Interpreter zugreifen. Dies bedeutet zwei Dinge: 1) Sie verwenden selten eine Lock-Anweisung in Python und 2) Wenn Sie Multiprozessorsysteme nutzen möchten, müssen Sie separate Prozesse verwenden. EDIT: Ich sollte auch darauf hinweisen, dass Sie einen Teil des Codes in C / C ++ einfügen können, wenn Sie auch die GIL umgehen möchten.

Daher müssen Sie überlegen, warum Sie Threads verwenden möchten. Wenn Sie Ihre App parallelisieren möchten, um die Dual-Core-Architektur zu nutzen, müssen Sie in Betracht ziehen, Ihre App in mehrere Prozesse aufzuteilen.

Wenn Sie die Reaktionsfähigkeit verbessern möchten, sollten Sie die Verwendung von Threads in Betracht ziehen. Es gibt jedoch auch andere Alternativen, nämlich Mikrothreading . Es gibt auch einige Frameworks, die Sie untersuchen sollten:

Jason Baker
quelle
@JS - Behoben. Diese Liste war sowieso veraltet.
Jason Baker
Es fühlt sich für mich einfach falsch an, dass Sie mehrere Prozesse benötigen - mit all dem damit verbundenen Aufwand -, um die Vorteile eines Multi-Core-Systems nutzen zu können. Wir haben einige Server mit 32 logischen Kernen - also brauche ich 32 Prozesse, um sie effizient zu nutzen? Wahnsinn
Basic
@Basic - Der Aufwand beim Starten eines Prozesses im Vergleich zum Starten eines Threads ist heutzutage minimal. Ich nehme an, Sie sehen möglicherweise Probleme, wenn wir über Tausende von Abfragen pro Sekunde sprechen, aber dann würde ich zunächst die Wahl von Python für einen so ausgelasteten Dienst in Frage stellen.
Jason Baker
20

Unten finden Sie ein grundlegendes Threading-Beispiel. Es werden 20 Threads erzeugt; Jeder Thread gibt seine Thread-Nummer aus. Führen Sie es aus und beobachten Sie die Reihenfolge, in der sie gedruckt werden.

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

Wie Sie bereits angedeutet haben, werden Python-Threads durch Time-Slicing implementiert. So erhalten sie den "parallelen" Effekt.

In meinem Beispiel erweitert meine Foo-Klasse den Thread. Anschließend implementiere ich die runMethode. Dort wird der Code abgelegt, den Sie in einem Thread ausführen möchten. Um den Thread zu starten, rufen Sie start()das Thread-Objekt auf, das automatisch die runMethode aufruft ...

Dies sind natürlich nur die Grundlagen. Möglicherweise möchten Sie mehr über Semaphoren, Mutexe und Sperren für die Thread-Synchronisierung und die Nachrichtenübermittlung erfahren.

mmattax
quelle
10

Verwenden Sie Threads in Python, wenn die einzelnen Worker E / A-gebundene Operationen ausführen. Wenn Sie versuchen, über mehrere Kerne auf einem Computer zu skalieren, finden Sie entweder ein gutes IPC- Framework für Python oder wählen Sie eine andere Sprache.

Ben McNiel
quelle
5

Hinweis: Wo immer ich erwähne, threadmeine ich speziell Threads in Python, bis dies ausdrücklich angegeben wird.

Threads funktionieren in Python etwas anders, wenn Sie aus dem C/C++Hintergrund kommen. In Python kann zu einem bestimmten Zeitpunkt nur ein Thread ausgeführt werden. Dies bedeutet, dass Threads in Python die Leistung mehrerer Verarbeitungskerne nicht wirklich nutzen können, da Threads aufgrund ihres Designs nicht parallel auf mehreren Kernen ausgeführt werden können.

Da die Speicherverwaltung in Python nicht threadsicher ist, erfordert jeder Thread einen exklusiven Zugriff auf Datenstrukturen im Python-Interpreter. Dieser exklusive Zugriff wird durch einen Mechanismus namens (Global Interpretr Lock) erworben .GIL

Why does python use GIL?

Um zu verhindern, dass mehrere Threads gleichzeitig auf den Interpreter-Status zugreifen und den Interpreter-Status beschädigen.

Die Idee ist, wann immer ein Thread ausgeführt wird (auch wenn es der Hauptthread ist) , eine GIL erfasst wird und nach einem vordefinierten Zeitintervall die GIL vom aktuellen Thread freigegeben und von einem anderen Thread (falls vorhanden) erneut erfasst wird.

Why not simply remove GIL?

Es ist nicht so, dass es unmöglich ist, GIL zu entfernen, es ist nur so, dass wir in diesem Fall mehrere Sperren in den Interpreter einfügen, um den Zugriff zu serialisieren, was selbst eine einzelne Thread-Anwendung weniger leistungsfähig macht.

Daher werden die Kosten für das Entfernen von GIL durch eine verringerte Leistung einer einzelnen Thread-Anwendung bezahlt, was niemals erwünscht ist.

So when does thread switching occurs in python?

Der Thread-Wechsel erfolgt, wenn GIL freigegeben wird. Wann wird GIL freigegeben? Es sind zwei Szenarien zu berücksichtigen.

Wenn ein Thread CPU-gebundene Operationen ausführt (Ex-Bildverarbeitung).

In älteren Python-Versionen wurde die Thread-Umschaltung nach einer festgelegten Anzahl von Python-Anweisungen durchgeführt. Standardmäßig wurde diese 100Option festgelegt. Es stellte sich heraus, dass es keine sehr gute Richtlinie ist, zu entscheiden, wann die Umschaltung erfolgen soll, da für die Ausführung einer einzelnen Anweisung Zeit aufgewendet wurde kann sehr wild von Millisekunde bis zu einer Sekunde sein. Daher ist die Freigabe von GIL nach jeder 100Anweisung unabhängig von der Zeit, die sie für die Ausführung benötigen, eine schlechte Richtlinie.

In neuen Versionen wird anstelle der Befehlsanzahl als Metrik zum Wechseln des Threads ein konfigurierbares Zeitintervall verwendet. Das Standardschaltintervall beträgt 5 Millisekunden. Sie können das aktuelle Schaltintervall mit abrufen sys.getswitchinterval(). Dies kann mit geändert werdensys.setswitchinterval()

Wenn ein Thread einige E / A-gebundene Operationen ausführt (Ex-Dateisystemzugriff oder
Netzwerk-E / A)

GIL wird freigegeben, wenn der Thread darauf wartet, dass der E / A-Vorgang abgeschlossen ist.

Which thread to switch to next?

Der Interpreter hat keinen eigenen Scheduler. Welcher Thread am Ende des Intervalls geplant wird, ist die Entscheidung des Betriebssystems. .

anekix
quelle
3

Eine einfache Lösung für die GIL ist das Multiprozessor- Modul. Es kann als Ersatz für das Threading-Modul verwendet werden, verwendet jedoch mehrere Interpreter-Prozesse anstelle von Threads. Aus diesem Grund ist der Aufwand für einfache Dinge etwas höher als für einfaches Threading, bietet Ihnen jedoch den Vorteil einer echten Parallelisierung, wenn Sie diese benötigen. Es lässt sich auch problemlos auf mehrere physische Maschinen skalieren.

Wenn Sie eine wirklich groß angelegte Parallelisierung benötigen, würde ich weiter schauen, aber wenn Sie nur auf alle Kerne eines Computers oder auf einige andere skalieren möchten, ohne all die Arbeit, die für die Implementierung eines umfassenderen Frameworks erforderlich wäre, dann ist dies für Sie .

willt
quelle
2

Denken Sie daran, dass die GIL von Zeit zu Zeit abgefragt wird, um das Erscheinungsbild mehrerer Aufgaben anzuzeigen. Diese Einstellung kann fein abgestimmt werden, aber ich biete den Vorschlag an, dass die Threads arbeiten sollten oder dass viele Kontextwechsel Probleme verursachen werden.

Ich würde so weit gehen, mehrere Eltern auf Prozessoren vorzuschlagen und zu versuchen, ähnliche Jobs auf demselben Kern zu behalten.

Phreaki
quelle