4

我正在编写一个 C 程序,它使用用 python 编写的网络库。我将 python lib 与 python C api 一起嵌入。库异步发送所有请求,并在请求完成时通过信号通知我。

那是理论上的意思。

实际上,我有两个与线程相关的问题:

  1. 从 c 对 python 库的所有调用都是块状的(它们应该立即返回)
  2. python 库异步调用注册的回调(thread.start_new_thread(callback,args))。这不起作用(没有任何反应)。如果我将 python 代码更改为 callback(args) 那么它确实有效。

我做错了什么?我必须做些什么才能使多线程工作吗?

4

1 回答 1

6

我有类似的情况。

初始工作流程

  1. 应用程序从 C++ 层开始
  2. C++层在主线程中调用Python层中的函数
  3. 主线程中的Python层函数创建事件线程
  4. 在 Python 层启动事件线程并返回 C++ 层
  5. 主循环从 C++ 层开始
  6. 如果需要,事件线程在 C++ 层调用回调函数

从一开始,事件线程就出乎意料地工作。我想这是由于我遇到的情况导致的 GIL,所以我试图从 GIL 解决这个问题。这是我的解决方案。

分析

首先,从PyEval_InitThreads中的注释,

当只存在主线程时,不需要 GIL 操作。...因此,最初没有创建锁。...

所以如果需要多线程,PyEval_InitThreads()必须在主线程中调用。而我PyEval_InitThreads()之前打电话Py_Initialize()。现在 GIL 已初始化,主线程获取 GIL。

其次,每次从 C++ 层调用 Python 函数之前,都会调用PyGILState_Ensure()GIL。另外,调用 Python 函数后,PyGILState_Release(state)会调用返回到之前的 GIL 状态。结果,在第 2 步之前PyGILState_Ensure()调用,在第 4 步之后PyGILState_Release(state)调用。

但有一个问题。从PyGILState_EnsurePyGILState_Release中,这两个函数是保存当前 GIL 状态以获取 GIL,并恢复之前的 GIL 状态以释放 GIL。但是,PyEval_InitThreads()在主线程中调用后,主线程肯定拥有 GIL。主线程中的 GIL 状态如下:

/* main thread owns GIL by PyEval_InitThreads */

state = PyGILState_Ensure();
/* main thread owns GIL by PyGILState_Ensure */

...
/* invoke Python function */
...

PyGILState_Release(state);
/* main thread owns GIL due to go back to previous state */

从上面的代码示例中,主线程始终拥有 GIL,因此事件线程永远不会运行。为了克服这种情况,让主线程在调用之前不获取 GIL PyGILState_Ensure()。因此,在调用之后PyGILState_Release(state),主线程可以释放 GIL 让事件线程运行。所以 GIL 初始化时应该立即在主线程中释放 GIL。

这里PyEval_SaveThread()使用。从PyEval_SaveThread

释放全局解释器锁(如果已创建并启用线程支持)并将线程状态重置为 NULL,...

通过这样做,使用多线程嵌入 Python 是可行的。

修改后的工作流程

  1. 应用程序从 C++ 层开始
  2. PyEval_InitThreads();启用多线程
  3. save = PyEval_SaveThread();在主线程中释放 GIL
  4. state = PyGILState_Ensure();在主线程中获取 GIL
  5. C++层在主线程中调用Python层中的函数
  6. 主线程中的Python层函数创建事件线程
  7. 在 Python 层启动事件线程并返回 C++ 层
  8. PyGILState_Release(state);在主线程中释放 GIL
  9. 主循环从 C++ 层开始
  10. 如果需要,事件线程在 C++ 层调用回调函数
于 2015-06-10T03:49:11.070 回答