Python C-Api 线程问题

2024-05-03

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

这意味着理论上。

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

  1. 从 c 对 python lib 的所有调用都是块状的(它们应该立即返回)
  2. python lib 异步调用已注册的回调 (thread.start_new_thread(callback, args))。这不起作用(什么也没有发生)。如果我将 python 代码更改为回调(args),那么它就可以工作。

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


我有类似的情况。

初始工作流程

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

从一开始,事件线程就以意想不到的方式工作。从我遇到的情况来看,我猜这是由于GIL造成的,所以我尝试从GIL解决这个问题。这是我的解决方案。

Analysis

首先,从注释中PyEval_InitThreads https://docs.python.org/2/c-api/init.html#c.PyEval_InitThreads,

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

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

其次,每次从 C++ 层调用 Python 函数之前,PyGILState_Ensure()被调用以获取GIL。另外,调用Python函数后,PyGILState_Release(state)被调用返回到之前的 GIL 状态。结果,在步骤2之前,PyGILState_Ensure()被调用,并且在步骤 4 之后,PyGILState_Release(state)叫做。

但有一个问题。从PyGILState_Ensure https://docs.python.org/2/c-api/init.html#c.PyGILState_Ensure and PyGILState_Release https://docs.python.org/2/c-api/init.html#c.PyGILState_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,因此事件线程永远不会运行。为了克服这种情况,让主线程在调用之前不获取GILPyGILState_Ensure()。因此,调用后PyGILState_Release(state),主线程可以释放GIL让事件线程运行。因此,当GIL初始化时,应立即在主线程中释放GIL。

Here PyEval_SaveThread()用来。从PyEval_SaveThread https://docs.python.org/2/c-api/init.html#c.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++层的回调函数
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python C-Api 线程问题 的相关文章

随机推荐