Parallelität: Sind in C/C++ geschriebene Python-Erweiterungen von der globalen Interpretersperre betroffen?

Lesezeit: 3 Minuten

Einer der stärksten Punkte von Python ist das einfache Schreiben von C- und C++-Erweiterungen, um prozessorintensive Teile des Codes zu beschleunigen. Können diese Erweiterungen den Global Interpreter Lock umgehen oder sind sie auch durch die GIL eingeschränkt? Wenn nicht, dann ist diese “einfache Erweiterung” ein noch größeres Killer-Feature, als ich zuvor erkannt habe. Ich vermute, die Antwort ist kein einfaches Ja oder Nein, aber ich bin mir nicht sicher, also stelle ich die Frage hier auf StackOverflow.

Ja, Aufrufe von C-Erweiterungen (von Python aufgerufene C-Routinen) unterliegen weiterhin der GIL.

Sie können jedoch manuell Geben Sie die GIL in Ihrer C-Erweiterung frei, solange Sie darauf achten, sie erneut zu bestätigen, bevor Sie die Kontrolle an die Python-VM zurückgeben.

Informationen finden Sie unter Py_BEGIN_ALLOW_THREADS und Py_END_ALLOW_THREADS Makros: http://docs.python.org/c-api/init.html#thread-state-and-the-global-interpreter-lock

  • Können Sie sich dieses Q ansehen und mir sagen, was Sie davon halten? stackoverflow.com/questions/53855880/…

    – Toni Salimi

    19. Dezember 2018 um 17:22 Uhr

Benutzer-Avatar
Ted Dziuba

C/C++-Erweiterungen für Python sind nicht an die GIL gebunden. Allerdings muss man wirklich wissen was man tut. Von http://docs.python.org/c-api/init.html:

Die globale Interpreter-Sperre wird verwendet, um den Zeiger auf den aktuellen Thread-Zustand zu schützen. Beim Freigeben der Sperre und Speichern des Thread-Zustands muss der aktuelle Thread-Zustandszeiger abgerufen werden, bevor die Sperre freigegeben wird (da ein anderer Thread sofort die Sperre erwerben und seinen eigenen Thread-Zustand in der globalen Variablen speichern könnte). Umgekehrt muss beim Erwerben der Sperre und Wiederherstellen des Thread-Zustands die Sperre erworben werden, bevor der Thread-Zustandszeiger gespeichert wird.

Warum gehe ich so detailliert darauf ein? Denn wenn Threads von C erstellt werden, haben sie weder die globale Interpreter-Sperre, noch gibt es eine Thread-Status-Datenstruktur für sie. Solche Threads müssen sich selbst ins Leben rufen, indem sie zuerst eine Thread-Zustandsdatenstruktur erstellen, dann die Sperre erwerben und schließlich ihren Thread-Zustandszeiger speichern, bevor sie mit der Verwendung der Python/C-API beginnen können. Wenn sie fertig sind, sollten sie den Thread-Zustandszeiger zurücksetzen, die Sperre freigeben und schließlich ihre Thread-Zustandsdatenstruktur freigeben.

  • Denkst du vielleicht an C-Code? bettet ein Python statt C-Erweiterungen? Wenn ein Thread im Python-Interpreter Ihre C-Erweiterung aufruft, enthält er immer noch die GIL, es sei denn, Sie geben sie ausdrücklich frei. In dem von Ihnen zitierten Abschnitt geht es um erstellte Threads außen Python überhaupt.

    – Tim Lesher

    16. März 2009 um 16:53 Uhr

  • Ich denke, das OP ist mehrdeutig. Sie können einen Thread in einer C-Erweiterung spinnen, die nicht an die GIL gebunden ist.

    – Ted Dziuba

    16. März 2009 um 17:40 Uhr

Schauen Sie sich Cython an, es hat eine ähnliche Syntax wie Python, aber mit ein paar Konstrukten wie “cdef”, schnellen numpy-Zugriffsfunktionen und einer “with nogil”-Anweisung (die tut, was sie sagt).

Wenn Sie Ihre Erweiterung in C++ schreiben, können Sie RAII verwenden, um einfach und lesbar Code zu schreiben, der die GIL manipuliert. Ich verwende dieses Paar RAII-Structlets:

namespace py {

    namespace gil {

        struct release {
            PyThreadState* state;
            bool active;

            release()
                :state(PyEval_SaveThread()), active(true)
                {}

            ~release() { if (active) { restore(); } }

            void restore() {
                PyEval_RestoreThread(state);
                active = false;
            }
        };

        struct ensure {
            PyGILState_STATE* state;
            bool active;

            ensure()
                :state(PyGILState_Ensure()), active(true)
                {}

            ~ensure() { if (active) { restore(); } }

            void restore() {
                PyGILState_Release(state);
                active = false;
            }
        };

    }

}

… ermöglicht, dass die GIL für einen bestimmten Block umgeschaltet werden kann (auf eine semantische Weise, die jedem Kontext-Manager-Pythonista-Fan vage bekannt vorkommen mag):

PyObject* YourPythonExtensionFunction(PyObject* self, PyObject* args) {

    Py_SomeCAPICall(…);     /// generally, if it starts with Py* it needs the GIL
    Py_SomeOtherCall(…);    /// ... there are exceptions, see the docs

    {
        py::gil::release nogil;
        std::cout << "Faster and less block-y I/O" << std::endl
                  << "can run inside this block -" << std::endl
                  << "unimpeded by the GIL";
    }

    Py_EvenMoreAPICallsForWhichTheGILMustBeInPlace(…);

}

… In der Tat finde ich persönlich auch die Leichtigkeit, Python zu erweitern, und das Maß an Kontrolle, das man über die internen Strukturen und den Zustand hat, ein Killer-Feature.

1373400cookie-checkParallelität: Sind in C/C++ geschriebene Python-Erweiterungen von der globalen Interpretersperre betroffen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy