Was ist die Python-GIL, wie funktioniert sie und wie wirkt sie sich auf Gunicorn aus?
Welchen Gunicorn-Arbeitertyp sollte ich für die Produktionsumgebung wählen?
Python verfügt über eine globale Sperre (GIL), die nur die Ausführung eines Threads zulässt (d. h. Bytecode interpretieren). Meiner Meinung nach ist es wichtig zu verstehen, wie Python mit Parallelität umgeht, wenn Sie Ihre Python-Dienste optimieren möchten.
Python und Gunicorn bieten Ihnen unterschiedliche Möglichkeiten, mit Parallelität umzugehen, und da es kein Allheilmittel gibt, das alle Anwendungsfälle abdeckt, ist es eine gute Idee, die Optionen, Kompromisse und Vorteile jeder Option zu verstehen.
Gunicorn stellt diese verschiedenen Optionen unter dem Konzept der „Arbeitertypen“ dar. Jeder Typ ist für eine bestimmte Reihe von Anwendungsfällen geeignet.
Dies ist die einfachste Art von Job, bei dem die einzige Parallelitätsoption darin besteht, N Prozesse zu forken und diese parallel auszuführen lokale Serviceanfrage.
Sie können gut funktionieren, verursachen aber viel Overhead (z. B. Speicher- und CPU-Kontextwechsel), und wenn die meiste Zeit Ihrer Anfrage darauf wartet, dass ich/ O, dann ist die Skalierbarkeit nicht gut.
gthread worker verbessert dies, indem es Ihnen ermöglicht, N Threads pro Prozess zu erstellen. Dies verbessert die E/A-Leistung, da Sie mehr Instanzen Ihres Codes gleichzeitig ausführen können. Dies ist der einzige der vier Fälle, der von GIL betroffen ist. #?? Verbessern Sie das Gthread-Modell weiter. Dadurch erhalten Sie Tausende dieser Greenlets zu einem Bruchteil der Kosten im Vergleich zu System-Threads. Ein weiterer Unterschied besteht darin, dass es eher einem kollaborativen als einem präventiven Arbeitsmodell folgt und eine ununterbrochene Arbeit ermöglicht, bis sie blockiert werden. Wir analysieren zunächst das Verhalten des Gthread-Worker-Threads bei der Verarbeitung von Anfragen und wie es durch die GIL beeinflusst wird.
Im Gegensatz zur Synchronisierung, bei der jede Anfrage direkt von einem Prozess bedient wird, verfügt bei gthread jeder Prozess über N Threads, um eine bessere Skalierung zu ermöglichen, ohne dass der Overhead mehrerer Prozesse anfällt. Da Sie mehrere Threads im selben Prozess ausführen, verhindert die GIL, dass sie parallel ausgeführt werden.
GIL ist kein Prozess oder spezieller Thread. Es handelt sich lediglich um eine boolesche Variable, deren Zugriff durch einen Mutex geschützt ist, der sicherstellt, dass in jedem Prozess nur ein Thread ausgeführt wird. Wie es funktioniert, sehen Sie im Bild oben. In diesem Beispiel können wir sehen, dass zwei Systemthreads gleichzeitig laufen, wobei jeder Thread eine Anfrage bearbeitet. Der Prozess ist wie folgt: Thread A hält die GIL und beginnt mit der Bearbeitung der Anfrage.
Nach einer Weile versucht Thread B, die Anfrage zu bedienen, kann die GIL jedoch nicht halten. B Legen Sie ein Timeout fest, um die Freigabe der GIL zu erzwingen, wenn dies nicht vor Erreichen des Timeouts geschieht.
A Die GIL wird erst freigegeben, wenn das Timeout erreicht ist. B setzt das Flag gil_drop_request, um A zu zwingen, die GIL sofort freizugeben.
Das bedeutet zwar, dass sie nicht von der GIL betroffen sind, es bedeutet aber auch, dass Sie die Parallelität immer noch nicht erhöhen können, da sie von der CPU nicht parallel geplant werden können.
In diesem Fall ist es offensichtlich, dass es nicht ideal ist, einen Greenlet-Arbeiter zu haben. Am Ende wartet die zweite Anfrage, bis die erste Anfrage abgeschlossen ist, und wartet dann wieder im Leerlauf auf E/A.
In diesen Szenarien glänzt das Greenlet-Kollaborationsmodell wirklich, weil Sie keine Zeit mit Kontextwechseln verschwenden und den Aufwand für die Ausführung mehrerer Systemthreads vermeiden.
Das werden wir im Benchmark-Test am Ende dieses Artikels erleben. Da stellt sich nun die folgende Frage:
Um diese Fragen zu beantworten, müssen Sie überwachen, um die erforderlichen Metriken zu erfassen und dann maßgeschneiderte Benchmarks mit denselben Metriken durchzuführen. Es hat keinen Sinn, synthetische Benchmarks auszuführen, die keine Korrelation zu Ihren tatsächlichen Nutzungsmustern aufweisen. Die folgende Grafik zeigt Latenz- und Durchsatzmetriken für verschiedene Szenarien, um Ihnen eine Vorstellung davon zu geben, wie alles zusammenwirkt.
Hier können wir sehen, wie sich die Änderung des GIL-Thread-Switching-Intervalls/Timeouts auf die Anforderungslatenz auswirkt. Wie erwartet wird die E/A-Latenz mit abnehmendem Switch-Intervall besser. Dies liegt daran, dass CPU-gebundene Threads gezwungen sind, die GIL häufiger freizugeben und anderen Threads die Möglichkeit zu geben, ihre Arbeit abzuschließen.
Aber das ist kein Allheilmittel. Durch die Reduzierung des Wechselintervalls dauert die Fertigstellung CPU-gebundener Threads länger. Wir können auch einen Anstieg der Gesamtlatenz und einen Rückgang der Zeitüberschreitungen aufgrund des erhöhten Overheads durch den ständigen Thread-Wechsel feststellen. Wenn Sie es selbst ausprobieren möchten, können Sie das Umschaltintervall mit dem folgenden Code ändern:
Insgesamt können wir sehen, dass der Benchmark die Intuition widerspiegelt abgeleitet aus unserer vorherigen Analyse der Funktionsweise von GIL-gebundenen Threads und Greenlets.
gthread hat eine bessere durchschnittliche Latenz für IO-gebundene Anfragen, da die Wechselintervalle lang laufende Threads zur Freigabe zwingen.
gevent CPU-gebundene Anfragen haben eine bessere Latenz als gthread, da sie nicht unterbrochen werden, um andere Anfragen zu bearbeiten.
Die Ergebnisse hier spiegeln auch unsere frühere Annahme wider, dass gevent einen besseren Durchsatz als gthread hat. Diese Benchmarks hängen stark von der Art der durchgeführten Arbeit ab und lassen sich nicht unbedingt direkt auf Ihren Anwendungsfall übertragen.
Das Hauptziel dieser Benchmarks besteht darin, Ihnen eine Anleitung zu geben, was Sie testen und messen müssen, um jeden CPU-Kern zu maximieren, der Anfragen bedient.
Da Sie bei allen Gunicorn-Workern die Anzahl der auszuführenden Prozesse angeben können, ändert sich die Art und Weise, wie jeder Prozess gleichzeitige Verbindungen verarbeitet. Achten Sie daher darauf, die gleiche Anzahl an Arbeitskräften einzusetzen, um den Test fair zu gestalten. Versuchen wir nun, die vorherige Frage anhand der aus unserem Benchmark gesammelten Daten zu beantworten.
In der Tat. Für die überwiegende Mehrheit der Workloads stellt dies jedoch keinen entscheidenden Faktor dar.
Wie wähle ich zwischen gevent/eventlet und gthread, wenn ich mit gemischten I/Os und CPUs arbeite? Wie wir sehen können, ermöglicht Ghtread tendenziell eine bessere Parallelität, wenn Sie CPU-intensivere Arbeit haben.
Solange Ihre Benchmarks ein produktionsähnliches Verhalten simulieren können, werden Sie die Spitzenleistung deutlich erkennen und dann aufgrund zu vieler Threads nachlassen.
Sollte ich einfach Sync-Worker verwenden und die Anzahl der gespaltenen Prozesse erhöhen, um die GIL zu vermeiden?
Sofern Ihr I/O nicht fast Null ist, ist die Skalierung nur mit Prozessen nicht die beste Option.
Coroutinen/Greenlets können die CPU-Effizienz verbessern, da sie Interrupts und Kontextwechsel zwischen Threads vermeiden. Coroutinen tauschen Latenz gegen Durchsatz.
Wenn Sie IO- und CPU-gebundene Endpunkte mischen, können Coroutinen zu unvorhersehbareren Latenzen führen – CPU-gebundene Endpunkte werden nicht unterbrochen, um andere eingehende Anfragen zu bearbeiten. Wenn Sie sich die Zeit nehmen, gunicorn richtig zu konfigurieren, ist die GIL kein Problem.
Das obige ist der detaillierte Inhalt vonGunicorn und Python GIL in einem Artikel verstehen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!