在多线程 C 应用程序中嵌入 python
Posted
技术标签:
【中文标题】在多线程 C 应用程序中嵌入 python【英文标题】:Embedding python in multithreaded C application 【发布时间】:2012-05-24 10:21:26 【问题描述】:我将 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()
的调用中很快死锁。
那么我在这里错过了什么?
【问题讨论】:
【参考方案1】:我遇到了完全相同的问题,现在通过在PyEval_InitThreads()
之后立即使用PyEval_SaveThread()
解决了,正如您在上面建议的那样。然而,我的实际问题是我在PyInitialise()
之后使用了PyEval_InitThreads()
,然后导致PyGILState_Ensure()
在从不同的后续本机线程调用时阻塞。总之,这就是我现在所做的:
有全局变量:
static int gil_init = 0;
从主线程加载原生 C 扩展并启动 Python 解释器:
Py_Initialize()
我的应用同时从多个其他线程对 Python/C API 进行大量调用:
if (!gil_init)
gil_init = 1;
PyEval_InitThreads();
PyEval_SaveThread();
state = PyGILState_Ensure();
// Call Python/C API functions...
PyGILState_Release(state);
从主线程停止 Python 解释器
Py_Finalize()
我尝试过的所有其他解决方案都使用PyGILState_Ensure()
导致随机 Python sigfaults 或死锁/阻塞。
Python 文档在这方面确实应该更清楚,并且至少为嵌入和扩展用例提供了一个示例。
【讨论】:
【参考方案2】:最终我想通了。 之后
PyEval_InitThreads();
你需要打电话
PyEval_SaveThread();
同时为主线程正确释放 GIL。
【讨论】:
这是错误的并且可能有害:PyEval_SaveThread
应始终与 PyEval_RestoreThread
结合使用。作为explained elsewhere,你不应该在初始化后尝试释放锁;只需将其留给 Python 来作为其常规工作的一部分发布即可。
我不明白如果将所有对 python 的调用放在 Block Allow 块中会有害。另一方面,如果您不调用PyEval_SaveThread();
,那么您的主线程将阻止其他线程访问Python。换句话说PyGILState_Ensure()
死锁。
这是唯一适用于嵌入 Python 和调用扩展模块的方法。
确实,PyEval_SaveThread()
必须由调用PyEval_InitThreads()
的线程调用,否则当线程尝试调用PyGILState_Ensure()
时会发生死锁(因为GIL 不可用于检索)。 PyEval_RestoreThread()
最终应该由调用PyEval_SaveThread()
的同一线程调用,但此时,重要的是所有可能调用PyGILState_Ensure()
的线程都已完成,或者可能发生死锁,原因相同。
【参考方案3】:
让一个多线程 C 应用程序尝试从多个线程与单个 CPython 实例的多个 Python 线程进行通信对我来说看起来很冒险。
只要只有一个 C 线程与 Python 通信,即使 Python 应用程序是多线程的,您也不必担心锁定问题。 如果您需要多个 Python 线程,您可以通过这种方式设置应用程序,并让多个 C 线程通过队列与单个 C 线程进行通信,然后将它们分配给多个 Python 线程。
另一种可能对您有用的方法是为每个需要它的 C 线程设置多个 CPython 实例(当然 Python 程序之间的通信应该通过 C 程序进行)。
另一种选择可能是 Stackless Python 解释器。这取消了 GIL,但我不确定您是否遇到了将其绑定到多个线程的其他问题。 stackless 是我的(单线程)C 应用程序的直接替代品。
【讨论】:
你尽力了,没有真正回答这个问题。我对将工作排队到单个线程不感兴趣。以上是关于在多线程 C 应用程序中嵌入 python的主要内容,如果未能解决你的问题,请参考以下文章
从 C/C++ 程序调用的多个操作系统线程上的多个独立嵌入式 Python 解释器