python - Python 3 : How/when to call it? 中的 PyEval_InitThreads(传奇继续令人作呕)

标签 python python-3.x python-c-api python-3.2 python-c-extension

基本上,对于 PyEval_InitThreads() 的确切时间,似乎存在大量混淆/歧义。应该被调用,并且需要什么伴随的 API 调用。 official Python documentation不幸的是非常模棱两可。已经有many questions on stackoverflow关于这个话题,事实上,我个人已经asked a question almost identical对于这个,所以如果它作为副本关闭,我不会特别惊讶;但考虑到这个问题似乎没有明确的答案。 (遗憾的是,我没有快速拨号的 Guido Van Rossum。)

首先,让我们在这里定义问题的范围:我想做什么?嗯...我想用 C 编写一个 Python 扩展模块,它将:

  1. 使用 C 中的 pthread API 生成工作线程
  2. 从这些 C 线程中调用 Python 回调

好的,让我们从 Python 文档本身开始。 Python 3.2 docs说:

void PyEval_InitThreads()

Initialize and acquire the global interpreter lock. It should be called in the main thread before creating a second thread or engaging in any other thread operations such as PyEval_ReleaseThread(tstate). It is not needed before calling PyEval_SaveThread() or PyEval_RestoreThread().

所以我的理解是:

  1. 任何产生线程的 C 扩展模块都必须调用 PyEval_InitThreads() 在任何其他线程之前从主线程 产生了
  2. 调用 PyEval_InitThreads 锁定 GIL

所以常识告诉我们,任何创建线程的C扩展模块都必须调用PyEval_InitThreads(),然后释放全局解释器锁。好吧,看起来很简单。所以初步看来,所需要的只是以下代码:

PyEval_InitThreads(); /* initialize threading and acquire GIL */
PyEval_ReleaseLock(); /* Release GIL */

看起来很简单...但不幸的是,Python 3.2 文档PyEval_ReleaseLock has been deprecated .相反,我们应该使用 PyEval_SaveThread为了释放 GIL:

PyThreadState* PyEval_SaveThread()

Release the global interpreter lock (if it has been created and thread support is enabled) and reset the thread state to NULL, returning the previous thread state (which is not NULL). If the lock has been created, the current thread must have acquired it.

呃...好吧,所以我猜一个 C 扩展模块需要说:

PyEval_InitThreads();
PyThreadState* st = PyEval_SaveThread();


确实,这正是 this stackoverflow answer说。除非我在实践中实际尝试,否则当我导入扩展模块时,Python 解释器会立即出现段错误。好的。


好的,所以现在我要放弃官方 Python 文档并转向 Google。所以, this random blog 声称您需要从扩展模块中调用 PyEval_InitThreads()。当然,文档声称 PyEval_InitThreads() 获得了 GIL,实际上是 quick inspection of the source code for PyEval_InitThreads() in ceval.c显示它确实调用了内部函数 take_gil(PyThreadState_GET());

所以 PyEval_InitThreads() 肯定 获得了 GIL。我会认为你绝对需要在调用 PyEval_InitThreads() 之后以某种方式释放 GIL。 但是如何? PyEval_ReleaseLock() 已被弃用,而 PyEval_SaveThread() 只是莫名其妙的段错误。

好的...所以可能出于某种目前我无法理解的原因,C 扩展模块不需要 需要释放 GIL。我试过了……正如预期的那样,一旦另一个线程尝试获取 GIL(使用 PyGILState_Ensure ),程序就会因死锁而挂起。所以是的......你真的需要在调用 PyEval_InitThreads() 之后释放 GIL。

同样,问题是:在调用 PyEval_InitThreads() 后如何释放 GIL? ?

更一般地说:C 扩展模块究竟需要做什么才能安全地从工作 C 线程调用 Python 代码?

最佳答案

您的理解是正确的:调用 PyEval_InitThreads 确实会获取 GIL。在正确编写的 Python/C 应用程序中,这不是问题,因为 GIL 会及时解锁,无论是自动还是手动。

如果主线程继续运行 Python 代码,没有什么特别的,因为 Python 解释器会在执行了一些指令后自动放弃 GIL(允许另一个线程获取它,这将再次放弃它) , 等等)。此外,每当 Python 即将调用阻塞系统调用时,例如从网络读取或写入文件,它会在调用周围释放 GIL。

这个答案的原始版本几乎到此结束。但还有一件事需要考虑:嵌入场景。

在嵌入 Python 时,主线程通常会初始化 Python 并继续执行其他与 Python 无关的任务。在那种情况下,没有什么会自动释放 GIL,所以这必须由线程本身来完成。这绝不是特定于调用 PyEval_InitThreads 的调用,它应该是 all Python/C code使用获取的 GIL 调用。

例如,main() 可能包含如下代码:

Py_Initialize();
PyEval_InitThreads();

Py_BEGIN_ALLOW_THREADS
... call the non-Python part of the application here ...
Py_END_ALLOW_THREADS

Py_Finalize();

如果你的代码手动创建线程,他们需要在做任何与 Python 相关的任何事情之前获取 GIL,甚至像 Py_INCREF 这样简单。为此,请使用 the following :

// Acquire the GIL
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();

... call Python code here ...

// Release the GIL. No Python API allowed beyond this point.
PyGILState_Release(gstate);

关于python - Python 3 : How/when to call it? 中的 PyEval_InitThreads(传奇继续令人作呕),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15470367/

相关文章:

python urllib 用法

python - 在单个 cmd 中运行多个命令,但有一些延迟

python - 在 Pandas 中执行模糊字符串匹配的更快方法

python - 在 Django Rest(嵌套序列化程序)中 POST 后,ForeignKey 为 null

python - 释放 PyTuple 对象

python - 如何在python pandas中使用df.loc和if条件删除一行

Python 带条件断言

python - 在 Py_BuildValue ("y#"之后是否需要 PyBuffer_Release,...)?

python - Boost.python 中的 error_already_set 做了什么以及如何在 Python C API 中类似地处理异常

python - 同一图中的多个子图与 networkx