CPython - 将GIL锁定在主线程中

Sil*_*ler 14 c python cpython

CPython线程支持的文档是令人沮丧的矛盾和稀疏.

通常,似乎每个人都同意嵌入Python的多线程C应用程序必须始终在调用Python解释器之前获取GIL.通常,这通过以下方式完成:

PyGILState_STATE s = PyGILState_Ensure();

/* do stuff with Python */

PyGILState_Release(s);
Run Code Online (Sandbox Code Playgroud)

这些文档很明显地说明了这一点:https://docs.python.org/2/c-api/init.html#non-python-created-threads

但是,在实践中,获得一个嵌入Python实际工作顺利的多线程C程序是另一回事.即使您完全遵循文档,似乎也有很多怪癖和惊喜.

例如,似乎在幕后,Python区分"主线程"(我猜是调用的线程Py_Initialize)和其他线程.具体来说,任何尝试获取GIL并在"主"线程中运行Python代码的尝试一直都失败了 - 当我尝试这样做时 - (至少使用Python 3.x),程序会中止一条Fatal Python error: drop_gil: GIL is not locked消息,这很愚蠢,因为当然GIL被锁定了!

例:

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;
}
Run Code Online (Sandbox Code Playgroud)

这个简单的程序以"GIL未锁定错误"中止,即使我明确锁定它.但是,如果我生成另一个线程,并尝试在该线程中获取GIL,一切正常.

所以CPython似乎有一个(未记录的)"主线程"概念,它与C产生的辅助线程有某种不同.

问题:这是否记录在任何地方?有没有人有任何经验可以说明获取GIL的具体规则是什么,如果处于"主要"线程而不是子线程应该对此有任何影响?

PS:另外,我已经注意到这PyEval_ReleaseLock是一个弃用的API调用,但我还没有看到任何实际可行的替代方案.如果您在通话PyEval_ReleaseLock后没有打电话PyEval_InitThreads,您的程序会立即挂起.然而,在该文档中提到的新的替代方案,PyEval_SaveThread也从来没有在实践中为我工作-它马上赛格故障,至少如果我把它称为"主"线程".

use*_*342 5

这个简单的程序会因“GIL 未锁定错误”而中止,即使我清楚地锁定了它。

您锁定了 GIL,但随后您继续在 中释放它PyGILState_Release,这意味着您在Py_Finalize 没有持有 GIL 的情况下调用了它。

有没有人有任何经验可以阐明获取 GIL 的确切规则

考虑 GIL 的预期方式是,一旦您调用PyEval_InitThreads(),就会有人始终持有 GIL,或者仅使用Py_BEGIN_ALLOW_THREADS和暂时释放它Py_END_ALLOW_THREADS。有关非常相似的混淆的扩展讨论,请参阅此答案

在您的情况下,编写示例程序的正确方法如下:

#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;
}
Run Code Online (Sandbox Code Playgroud)