Python C-API线程问题

4
我正在编写一份使用 Python 编写的网络库的 C 语言程序。我使用了 Python C API 将 Python 库嵌入到程序中。该库会异步发送所有请求,并在请求完成时通过信号通知我。
理论上,这意味着:
实际上,我遇到了两个与线程相关的问题:
1. 所有从 C 语言调用 Python 库的操作都是阻塞的(它们应该立即返回)。 2. Python 库以异步方式调用已注册的回调函数(thread.start_new_thread(callback, args))。但是无法正常工作(没有发生任何事情)。如果我将 Python 代码更改为 callback(args),那么就可以正常工作。
我做错了什么?是否有什么需要做才能使多线程工作?

1
你的 C 程序是否支持多线程?如果是,它在调用 Python 前是否会先调用 PyGILState_Ensure 函数? - user4815162342
PyEval_InitThreads(); 也许也需要被调用。 - Alex
http://docs.python.org/2/c-api/init.html#thread-state-and-the-global-interpreter-lock - Ignacio Vazquez-Abrams
我的程序不是多线程的。只有Python库是多线程的。那我需要关注这个吗? - Alexander Theißen
所有的手册和示例都是关于C应用程序在多线程时的情况。但这是相反的情况。 - Alexander Theißen
我这里也有同样的问题。找到什么了吗? - Markus
1个回答

6
我有类似的情况。
初始工作流程
1. 应用程序从C++层开始 2. C++层调用Python层中的函数(在主线程中) 3. Python层中的主线程函数创建了一个事件线程 4. 在Python层中启动事件线程并返回到C++层 5. C++层中开始主循环 6. 如果需要,事件线程会在C++层中调用回调函数
从一开始,事件线程的工作效果出人意料。 我猜这是由于我遇到的情况是GIL引起的,因此我尝试从GIL解决这个问题。 这是我的解决方案。
分析
首先,从PyEval_InitThreads的注释中可以看出,
当仅存在主线程时,不需要进行GIL操作。 因此,在初始情况下不会创建锁。 ...
因此,必须在主线程中调用PyEval_InitThreads(),以便进行多线程操作。 我在Py_Initialize()之前调用了PyEval_InitThreads()。 现在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,因此事件线程永远不会运行。为了克服这种情况,在调用PyGILState_Ensure()之前,让主线程不获取GIL。因此,在调用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++层的回调函数

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接