Warum wurde Python mit der GIL geschrieben?

112

Die globale Interpreter-Sperre (GIL) wird anscheinend häufig als Hauptgrund dafür angeführt, dass Threading und dergleichen in Python schwierig ist - was die Frage aufwirft, warum dies überhaupt geschehen ist.

Da ich kein Programmierer bin, habe ich keine Ahnung, warum das so ist - was war die Logik, die dahinter steckt, die GIL einzufügen?

Fomite
quelle
10
Der Wikipedia - Artikel heißt es, dass „die GIL eine signifikante Barriere für Parallelität-a sein Preis bezahlt für die Dynamik der Sprache mit“ , und führt weiter aus , dass „Gründe für den Einsatz einer solchen Sperre beinhalten: erhöhte Geschwindigkeit der Single-Threaded - Programme (keine Notwendigkeit, Sperren für alle Datenstrukturen separat zu erwerben oder freizugeben) und einfache Integration von C-Bibliotheken, die normalerweise nicht thread-sicher sind. "
Robert Harvey
3
@ Robert Harvey, Dynamik hat nichts damit zu tun. Das Problem ist die Mutation.
dan_waterworth
1
Ich kann nicht anders, als zu glauben, dass Java keine vorzeichenlosen Zahlen hat. Es sollte verhindern, dass Leute, die nicht wissen, was sie tun, sich selbst in den Fuß schießen. Leider bekommt jeder, der weiß , was er tut, eine mangelhafte Sprache, was wirklich schade ist, weil Python auf so viele andere Arten rockt
Basic
1
@Basic Es muss eine Standardmethode geben, um mit Byte-Arrays in Java umzugehen (ich habe sie lange nicht mehr verwendet), um Kryptomathematik zu betreiben. Python (zum Beispiel) hat keine vorzeichenbehafteten Nummern, aber ich würde nicht einmal versuchen, bitweise Operationen damit durchzuführen, weil es bessere Möglichkeiten gibt.
Nick T

Antworten:

105

Es gibt verschiedene Implementierungen von Python, z. B. CPython, IronPython, RPython usw.

Einige von ihnen haben eine GIL, andere nicht. Zum Beispiel hat CPython die GIL:

Aus http://en.wikipedia.org/wiki/Global_Interpreter_Lock

In Programmiersprachen mit einer GIL geschriebene Anwendungen können so entworfen werden, dass separate Prozesse verwendet werden, um eine vollständige Parallelität zu erreichen, da jeder Prozess seinen eigenen Interpreter und wiederum seine eigene GIL hat.

Vorteile der GIL

  • Erhöhte Geschwindigkeit von Single-Thread-Programmen.
  • Einfache Integration von C-Bibliotheken, die normalerweise nicht threadsicher sind.

Warum verwendet Python (CPython und andere) die GIL?

In CPython ist die globale Interpretersperre (GIL) ein Mutex, der verhindert, dass mehrere native Threads Python-Bytecodes gleichzeitig ausführen. Diese Sperre ist hauptsächlich notwendig, weil die Speicherverwaltung von CPython nicht threadsicher ist.

Die GIL ist umstritten, da sie verhindert, dass Multithread-CPython-Programme in bestimmten Situationen die Multiprozessorsysteme voll ausnutzen. Beachten Sie, dass potenziell blockierende oder lang andauernde Vorgänge wie E / A, Bildverarbeitung und NumPy-Zahlenverarbeitung außerhalb der GIL stattfinden. Daher wird nur in Multithread-Programmen, die viel Zeit in der GIL verbringen und den CPython-Bytecode interpretieren, die GIL zu einem Engpass.

Python hat aus mehreren Gründen eine GIL im Gegensatz zu einer feinkörnigen Sperrung:

  • Im Single-Threaded-Fall ist es schneller.

  • Im Multithread-Fall ist es für i / o-gebundene Programme schneller.

  • Im Multithreading-Fall ist es schneller, wenn cpu-gebundene Programme ihre rechenintensive Arbeit in C-Bibliotheken ausführen.

  • Dies erleichtert das Schreiben von C-Erweiterungen: Python-Threads werden nur dort umgeschaltet, wo Sie dies zulassen (dh zwischen den Makros Py_BEGIN_ALLOW_THREADS und Py_END_ALLOW_THREADS).

  • Dies erleichtert das Umschließen von C-Bibliotheken. Sie müssen sich keine Sorgen um die Thread-Sicherheit machen. Wenn die Bibliothek nicht threadsicher ist, lassen Sie die GIL einfach gesperrt, während Sie sie aufrufen.

Die GIL kann durch C-Erweiterungen freigegeben werden. Die Standardbibliothek von Python gibt die GIL für jeden blockierenden E / A-Aufruf frei. Somit hat die GIL keine Konsequenzen für die Leistung von I / O-gebundenen Servern. Auf diese Weise können Sie Netzwerkserver in Python mithilfe von Prozessen (Fork), Threads oder asynchronen E / A erstellen, und die GIL wird nicht in die Quere kommen.

Numerische Bibliotheken in C oder Fortran können ebenfalls mit der veröffentlichten GIL aufgerufen werden. Während Ihre C-Erweiterung auf den Abschluss einer FFT wartet, führt der Interpreter andere Python-Threads aus. Eine GIL ist somit auch in diesem Fall einfacher und schneller als eine feinkörnige Verriegelung. Dies macht den Großteil der numerischen Arbeit aus. Die NumPy-Erweiterung gibt die GIL nach Möglichkeit frei.

Threads sind normalerweise ein schlechter Weg, um die meisten Serverprogramme zu schreiben. Bei geringer Last ist das Gabeln einfacher. Bei hoher Auslastung ist eine asynchrone E / A- und ereignisgesteuerte Programmierung (z. B. mit dem Twisted-Framework von Python) besser. Die einzige Entschuldigung für die Verwendung von Threads ist das Fehlen von os.fork unter Windows.

Die GIL ist nur dann ein Problem, wenn Sie in reinem Python CPU-intensive Arbeit leisten. Hier können Sie mithilfe von Prozessen und Message-Passing (z. B. mpi4py) ein saubereres Design erzielen. Es gibt auch ein "Processing" -Modul in Python Cheese Shop, das Prozessen die gleiche Schnittstelle wie Threads gibt (dh threading.Thread durch processing.Process ersetzen).

Threads können verwendet werden, um die Reaktionsfähigkeit einer GUI unabhängig von der GIL aufrechtzuerhalten. Wenn die GIL Ihre Leistung beeinträchtigt (siehe obige Diskussion), können Sie Ihren Thread einen Prozess spawnen lassen und warten, bis er beendet ist.

Md Mahbubur Rahman
quelle
52
Klingt für mich nach sauren Trauben. Python kann Threads nicht richtig ausführen, sodass Sie Gründe dafür finden, warum Threads unnötig oder sogar schlecht sind. "Wenn die Last niedrig ist, ist das Gabeln einfacher", im Ernst? Und die GIL ist in all diesen Fällen nur dann "schneller", wenn Sie auf der Verwendung der Referenzzähl-GC bestehen.
Michael Borgwardt
9
s/RPython/PyPy/g. @MichaelBorgwardt Gründe für GIL zu nennen, ist eine Frage, nicht wahr? Ich stimme jedoch zu, dass ein Teil des Inhalts dieser Antwort (nämlich die Erörterung von Alternativen) nicht relevant ist. Und zum Guten oder Schlechten ist es jetzt fast unmöglich, das Nachzählen wieder loszuwerden - es ist tief in der gesamten API- und Codebasis verankert. Es ist fast unmöglich, es loszuwerden, ohne den halben Code neu zu schreiben und den gesamten externen Code zu brechen .
10
Vergessen Sie nicht die multiprocessingBibliothek - Standard seit 2.6. Die Worker-Pools sind für einige einfache Arten von Parallelität eine sehr raffinierte Abstraktion.
Sean McSomething
8
@alcalde Nur wenn Sie nicht wissen, was Sie tun und / oder nicht möchten, dass Ihre Threads kooperativ arbeiten / kommunizieren können. Ansonsten ist es ein großer Schmerz, vor allem wenn man bedenkt, dass bei einigen Betriebssystemen ein neuer Prozess gestartet werden muss. Wir haben Server mit 32 Kernen. Um sie in CPython voll auszunutzen, brauche ich 32 Prozesse. Das ist keine "gute Lösung", es ist ein Hack, um CPythons Unzulänglichkeiten zu umgehen.
Basic
8
Die Tatsache, dass Threads auf anderen Plattformen als Windows vorhanden sind, sollte den Nachweis erbringen, dass das Forking nicht in jeder Situation angemessen ist.
Zneak
42

Zunächst einmal: Python hat keine GIL. Python ist eine Programmiersprache. Eine Programmiersprache ist ein Satz abstrakter mathematischer Regeln und Einschränkungen. In der Python-Sprachspezifikation gibt es nichts, was besagt, dass es eine GIL geben muss.

Es gibt viele verschiedene Implementierungen von Python. Einige haben eine GIL, andere nicht.

Eine einfache Erklärung für eine GIL ist, dass das Schreiben von gleichzeitigem Code schwierig ist. Wenn Sie Ihren Code mit einem riesigen Schloss versehen, wird er immer seriell ausgeführt. Problem gelöst!

Insbesondere in CPython ist es ein wichtiges Ziel, die Erweiterung des Interpreters um in C geschriebene Plugins zu vereinfachen. Auch hier ist das Schreiben von gleichzeitigem Code schwierig. Dadurch, dass sichergestellt wird, dass es keine Parallelität gibt, wird das Schreiben von Erweiterungen vereinfacht der Dolmetscher. Außerdem handelt es sich bei vielen dieser Erweiterungen nur um dünne Wrapper um vorhandene Bibliotheken, die möglicherweise nicht unter Berücksichtigung der Parallelität geschrieben wurden.

Jörg W. Mittag
quelle
6
Das ist das gleiche Argument wie Javas Mangel an vorzeichenlosen numerischen Typen - die Entwickler denken, dass alle anderen dümmer sind als sie ...
Basic
1
@Basic - ob Sie es glauben oder nicht, selbst wenn Sie nicht wirklich, wirklich dumm sind, stellt sich heraus, dass es immer noch nützlich ist, eine Sprache zu haben, die vereinfachende Annahmen macht, die bedeuten, dass Sie nicht über bestimmte Dinge nachdenken, damit sie funktionieren Ding. CPython eignet sich hervorragend für bestimmte Dinge, einschließlich einfacher Multithread-Anwendungen (bei denen das Programm an E / A gebunden ist, was viele sind, und daher spielt die GIL keine Rolle), da die Entwurfsentscheidungen, die die GIL zur besten Lösung gemacht haben, auch die Programmierung dieser Anwendungen vereinfachen insbesondere die Tatsache, dass es atomare Operationen für Sammlungen unterstützt .
Jules
@Jules Ja, es ist sehr praktisch, bis Sie diese Funktionen benötigen. cpythons "bevorzugte" Lösung von "schreiben Sie es einfach in einer anderen Sprache wie c ++" bedeutet dann, dass Sie jeden einzelnen Python-Vorteil verlieren. Wenn Sie die Hälfte Ihres Codes in c ++ schreiben, warum sollten Sie dann mit Python beginnen? Sicher, für kleine API / Glue-Projekte ist es schnell und einfach, und für ETL ist es unübertroffen, aber es ist nicht für alles geeignet, was schweres Heben erfordert. Genau wie bei der Verwendung von Java für die Kommunikation mit Hardware ... Es ist beinahe komisch, durch welche Rahmen Sie springen müssen.
Grund
16

Was ist der Zweck einer GIL?

Die CAPI-Dokumentation hat zu diesem Thema folgendes zu sagen:

Der Python-Interpreter ist nicht vollständig threadsicher. Um Multithread-Python-Programme zu unterstützen, gibt es eine globale Sperre, die als globale Interpretersperre oder GIL bezeichnet wird und vom aktuellen Thread gehalten werden muss, damit er sicher auf Python-Objekte zugreifen kann. Ohne die Sperre können selbst die einfachsten Vorgänge Probleme in einem Multithread-Programm verursachen: Wenn beispielsweise zwei Threads gleichzeitig den Referenzzähler desselben Objekts erhöhen, wird der Referenzzähler möglicherweise nur einmal statt zweimal erhöht.

Mit anderen Worten, die GIL verhindert die Korruption des Staates. Python-Programme sollten niemals einen Segmentierungsfehler erzeugen, da nur speichersichere Operationen zulässig sind. Die GIL erweitert diese Sicherheit auf Multithread-Programme.

Was sind die Alternativen?

Wenn der Zweck der GIL darin besteht, den Staat vor Korruption zu schützen, dann ist eine naheliegende Alternative, sich auf ein viel feineres Korn festzulegen. Vielleicht auf Objektebene. Das Problem dabei ist, dass, obwohl nachgewiesen wurde, dass es die Leistung von Multithread-Programmen erhöht, der Overhead zunimmt und Single-Thread-Programme darunter leiden.

dan_waterworth
quelle
2
Es wäre großartig, wenn ein Benutzer ein Programm mit einer Interpreter-Option ausführen würde, die das Gil für eine feinkörnige Sperre ersetzt, und auf einfache Weise wissen würde, ob der aktuelle Prozess mit oder ohne Gil ausgelöst wurde.
Luis Masuelli
Trotz GIL konnte ich in einem Multithread-Programm einen Segmentierungsfehler erzeugen, weil das Modul pyodbc nachlässig verwendet wurde. Somit ist "sollte niemals ein Segmentierungsfehler erzeugt werden" ein Irrtum.
Muposat