19

我将 python 解释器嵌入到多线程 C 应用程序中,我对应该使用哪些 API 来确保线程安全感到有些困惑。

根据我收集到的信息,在嵌入 python 时,在调用任何其他 Python C API 调用之前,由嵌入器负责处理 GIL 锁。这是通过以下功能完成的:

gstate = PyGILState_Ensure();
// do some python api calls, run python scripts
PyGILState_Release(gstate);

但这似乎还不够。我仍然遇到随机崩溃,因为它似乎没有为 Python API 提供互斥。

在阅读了更多文档后,我还添加了:

PyEval_InitThreads();

就在调用之后,Py_IsInitialized()但这就是令人困惑的部分。文档指出此功能:

初始化并获取全局解释器锁

这表明当这个函数返回时,GIL 应该被锁定并且应该以某种方式解锁。但实际上这似乎不是必需的。有了这条线,我的多线程工作完美,并且功能保持互斥PyGILState_Ensure/Release
当我在应用程序在随后的调用中很快死锁PyEval_ReleaseLock()后尝试添加.PyEval_ReleaseLock()PyImport_ExecCodeModule()

那么我在这里错过了什么?

4

4 回答 4

8

我遇到了完全相同的问题,现在可以通过使用PyEval_SaveThread()after 立即解决PyEval_InitThreads(),正如您上面建议的那样。但是,我的实际问题是我使用PyEval_InitThreads()PyInitialise()它,然后PyGILState_Ensure()在从不同的后续本机线程调用时导致阻塞。总之,这就是我现在所做的:

  1. 有全局变量:

    static int gil_init = 0; 
    
  2. 从主线程加载本机 C 扩展并启动 Python 解释器:

    Py_Initialize() 
    
  3. 从多个其他线程,我的应用程序同时对 Python/C API 进行大量调用:

    if (!gil_init) {
        gil_init = 1;
        PyEval_InitThreads();
        PyEval_SaveThread();
    }
    state = PyGILState_Ensure();
    // Call Python/C API functions...    
    PyGILState_Release(state);
    
  4. 从主线程停止 Python 解释器

    Py_Finalize()
    

我尝试过的所有其他解决方案要么导致随机 Python sigfaults 要么使用PyGILState_Ensure().

Python 文档确实应该对此更加清楚,并且至少为嵌入和扩展用例提供了一个示例。

于 2014-01-26T15:56:22.823 回答
4

最终我想通了。

PyEval_InitThreads();

你需要打电话

PyEval_SaveThread();

同时正确释放主线程的 GIL。

于 2012-05-22T12:33:10.907 回答
0

请注意,if (!gil_init) {@forman 答案中的代码只运行一次,因此它可以在主线程中完成,这允许我们删除标志(gil_init必须正确地是原子的或以其他方式同步的)。

PyEval_InitThreads()仅在 CPython 3.6 及更早版本中才有意义,并且在 CPython 3.9 中已弃用,因此必须使用宏对其进行保护。

鉴于这一切,我目前使用的是以下内容:

在主线程中,运行所有

Py_Initialize();
PyEval_InitThreads(); // only on Python 3.6 or older!
/* tstate = */ PyEval_SaveThread(); // maybe save the return value if you need it later

现在,每当您需要调用 Python 时,请执行

state = PyGILState_Ensure();
// Call Python/C API functions...    
PyGILState_Release(state);

最后,从主线程,停止 Python 解释器

PyGILState_Ensure(); // PyEval_RestoreThread(tstate); seems to work just as well
Py_Finalize()
于 2022-01-22T18:16:56.673 回答
-1

让一个多线程 C 应用程序尝试从多个线程与单个 CPython 实例的多个 Python 线程进行通信对我来说看起来很冒险。

只要只有一个 C 线程与 Python 通信,即使 Python 应用程序是多线程的,您也不必担心锁定问题。如果您需要多个 python 线程,您可以通过这种方式设置应用程序,并让多个 C 线程通过队列与单个 C 线程进行通信,该单个 C 线程将它们分配给多个 Python 线程。

一种可能对您有用的替代方法是为每个需要它的 C 线程设置多个 CPython 实例(当然 Python 程序之间的通信应该通过 C 程序进行)。

另一种选择可能是 Stackless Python 解释器。这取消了 GIL,但我不确定您是否遇到了将其绑定到多个线程的其他问题。stackless 是我的(单线程)C 应用程序的直接替代品。

于 2012-05-17T05:50:12.253 回答