【问题标题】:CPython - locking the GIL in the main threadCPython - 在主线程中锁定 GIL
【发布时间】:2014-08-21 08:55:12
【问题描述】:

CPython 线程支持的文档令人沮丧地相互矛盾和稀疏。

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

PyGILState_STATE s = PyGILState_Ensure();

/* do stuff with Python */

PyGILState_Release(s);

文档非常清楚地说明了这一点: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;
}

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

因此,CPython 似乎有一个(未记录的)“主线程”概念,它与 C 产生的辅助线程有所不同。

问题:这是否记录在任何地方?有没有人有任何经验可以阐明获取 GIL 的确切规则是什么,以及是否应该在“主”线程与子线程中对此有任何影响?

PS:另外,我注意到PyEval_ReleaseLockdeprecated API call,但我还没有看到任何实际可行的替代方案。如果您在调用PyEval_InitThreads 之后不调用PyEval_ReleaseLock,您的程序会立即挂起。但是,newer alternative mentioned in the docsPyEval_SaveThread 在实践中从未对我有用 - 它会立即出现 seg 错误,至少如果我在“主”线程中调用它的话。

【问题讨论】:

标签: python c cpython


【解决方案1】:

这个简单的程序以“GIL 未锁定错误”中止,即使我明确锁定了它。

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

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

考虑 GIL 的预期方式是,一旦您调用 PyEval_InitThreads()某人 始终持有 GIL,或者仅使用 Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS 临时释放它。请参阅this answer 以了解对非常相似的混淆的详细讨论。

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

#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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多