CPython – GIL im Hauptthread sperren

Lesezeit: 3 Minuten

Die Dokumentation für die Unterstützung von CPython-Threads ist frustrierend widersprüchlich und spärlich.

Im Allgemeinen scheinen sich alle einig zu sein, dass Multithread-C-Anwendungen, die Python einbetten, immer die GIL erwerben müssen, bevor sie den Python-Interpreter aufrufen. Typischerweise geschieht dies durch:

PyGILState_STATE s = PyGILState_Ensure();

/* do stuff with Python */

PyGILState_Release(s);

Die Dokumente schreiben das ziemlich deutlich: https://docs.python.org/2/c-api/init.html#non-python-created-threads

In der Praxis ist es jedoch eine andere Geschichte, ein Multithread-C-Programm zu bekommen, das Python einbettet, um tatsächlich reibungslos zu funktionieren. Es scheint viele Macken und Überraschungen zu geben, auch wenn Sie sich genau an die Dokumentation halten.

Zum Beispiel scheint Python hinter den Kulissen zwischen dem “Haupt-Thread” zu unterscheiden (was meiner Meinung nach der Thread ist, der aufruft Py_Initialize) und andere Threads. Insbesondere ist jeder Versuch, die GIL zu erwerben und Python-Code im “Haupt”-Thread auszuführen, durchweg fehlgeschlagen, wenn ich dies versuche – (zumindest mit Python 3.x), das Programm bricht mit a ab Fatal Python error: drop_gil: GIL is not locked Nachricht, die dumm ist, weil natürlich die GIL gesperrt ist!

Beispiel:

int main()
{
    Py_Initialize();
    PyEval_InitThreads();
    PyEval_ReleaseLock();

    assert(PyEval_ThreadsInitialized());

    PyGILState_STATE s = PyGILState_Ensure();

    const char* command = "x = 5\nfor i in range(0,10): print(x*i)";
    PyRun_SimpleString(command);

    PyGILState_Release(s);
    Py_Finalize();

    return 0;
}

Dieses einfache Programm bricht mit einem „GIL ist nicht gesperrt“-Fehler ab, obwohl ich es eindeutig gesperrt habe. Wenn ich jedoch einen anderen Thread spawne und versuche, die GIL in diesem Thread zu erwerben, funktioniert alles.

CPython scheint also ein (undokumentiertes) Konzept eines “Hauptthreads” zu haben, das sich irgendwie von sekundären Threads unterscheidet, die von C hervorgebracht werden.

Frage: Ist das irgendwo dokumentiert? Hat jemand irgendwelche Erfahrungen gemacht, die Aufschluss darüber geben würden, was genau die Regeln für den Erwerb der GIL sind, und ob es einen Einfluss darauf haben soll, im “Haupt”-Thread im Vergleich zu einem untergeordneten Thread zu sein?

PS: Das habe ich auch notiert PyEval_ReleaseLock ist ein veralteter API-Aufruf, aber ich habe noch keine Alternative gesehen, die tatsächlich funktioniert. Wenn Sie nicht anrufen PyEval_ReleaseLock nach Anruf PyEval_InitThreads, hängt sich Ihr Programm sofort auf. Allerdings ist die neuere Alternative in den Dokumenten erwähnt, PyEval_SaveThread hat bei mir in der praxis nie funktioniert – es segnen sofort fehler, zumindest wenn ich es im “haupt”thread aufrufe”.

  • Diese Frage impliziert, dass Sie PyEval_ReleaseLock überhaupt nicht aufrufen sollten: stackoverflow.com/q/15470367/321772

    – Adam

    30. Juni 2014 um 22:13 Uhr

  • Sie sollten wahrscheinlich fragen python-dev.

    – Kaleb Hattingh

    1. Juli 2014 um 1:09 Uhr

Dieses einfache Programm bricht mit einem „GIL ist nicht gesperrt“-Fehler ab, obwohl ich es eindeutig gesperrt habe.

Sie haben die GIL gesperrt, aber dann haben Sie sie freigegeben PyGILState_Releasewas bedeutet, dass Sie aufgerufen haben Py_Finalize ohne die GIL hielt.

Hat jemand irgendwelche Erfahrungen gemacht, die Aufschluss darüber geben würden, was genau die Regeln für den Erwerb der GIL sind?

Die beabsichtigte Art, an die GIL zu denken, ist die, sobald Sie sie aufrufen PyEval_InitThreads(), jemand hält immer die GIL, oder hat sie nur zeitweilig mit freigegeben Py_BEGIN_ALLOW_THREADS und Py_END_ALLOW_THREADS. Siehe diese Antwort für eine ausführliche Diskussion einer sehr ähnlichen Verwirrung.

In Ihrem Fall wäre die richtige Art, das Beispielprogramm zu schreiben, wie folgt:

#include <Python.h>

static void various()
{
    // here we don't have the GIL and can run non-Python code without
    // blocking Python

    PyGILState_STATE s = PyGILState_Ensure();
    // from this line, we have the GIL, and we can run Python code

    const char* command = "x = 5\nfor i in range(0,10): print(x*i)";
    PyRun_SimpleString(command);

    PyGILState_Release(s);
    // from this line, we no longer have the GIL
}

int main()
{
    Py_Initialize();
    PyEval_InitThreads();
    // here we have the GIL
    assert(PyEval_ThreadsInitialized());

    Py_BEGIN_ALLOW_THREADS
    // here we no longer have the GIL, although various() is free to
    // (temporarily) re-acquire it
    various();
    Py_END_ALLOW_THREADS

    // here we again have the GIL, which is why we can call Py_Finalize 
    Py_Finalize();

    // at this point the GIL no longer exists
    return 0;
}

1035790cookie-checkCPython – GIL im Hauptthread sperren

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

Privacy policy