在多线程 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 解释器

在多线程 C++11 程序中未处理异常时会发生啥?

在多线程 C++11 程序中未处理异常时会发生啥?

C:在多线程程序中使用clock()测量时间

C++11:在多线程程序中使用局部静态变量导致 coredump

在多线程程序中添加 time.sleep 解决了 python 中的 UnicodeDecodeError