基本上好像有massive对确切时间的困惑/模糊PyEval_InitThreads()应该被调用,以及需要哪些伴随的 API 调用。这Python 官方文档不幸的是非常含糊。已经有stackoverflow上有很多问题关于这个话题,事实上,我个人已经问了一个几乎相同的问题对于这个,所以如果它作为重复项关闭,我不会感到特别惊讶;但请考虑一下这个问题似乎没有明确的答案。 (遗憾的是,我的快速拨号上没有 Guido Van Rossum。)
首先,我们先定义一下问题的范围:我想做什么?嗯...我想用 C 语言编写一个 Python 扩展模块,它将:
- 使用以下命令生成工作线程
pthread
C语言API
- 从这些 C 线程中调用 Python 回调
好的,让我们从 Python 文档本身开始。这Python 3.2 文档 say:
无效 PyEval_InitThreads()
初始化并获取全局解释器锁。它应该是
在创建第二个线程或参与之前在主线程中调用
在任何其他线程操作中,例如 PyEval_ReleaseThread(tstate)。
在调用 PyEval_SaveThread() 或之前不需要
PyEval_RestoreThread()。
所以我在这里的理解是:
- 任何生成线程的 C 扩展模块都必须调用
PyEval_InitThreads()
在任何其他线程之前从主线程开始
产生
- Calling
PyEval_InitThreads
锁定GIL
所以常识告诉我们,任何创建线程的 C 扩展模块都必须调用PyEval_InitThreads()
,然后释放全局解释器锁。好吧,看起来很简单。所以初步证据,所需要的只是以下代码:
PyEval_InitThreads(); /* initialize threading and acquire GIL */
PyEval_ReleaseLock(); /* Release GIL */
看起来很简单......但不幸的是,Python 3.2 文档also比如说PyEval_ReleaseLock已经已弃用。相反,我们应该使用PyEval_SaveThread为了释放 GIL:
PyThreadState* PyEval_SaveThread()
释放全局解释器锁(如果已经创建并且线程
支持已启用)并将线程状态重置为 NULL,返回
先前的线程状态(不为 NULL)。如果锁已被
创建后,当前线程必须已获取它。
呃...好吧,所以我想 C 扩展模块需要说:
PyEval_InitThreads();
PyThreadState* st = PyEval_SaveThread();
确实,这正是这个 stackoverflow 答案说。除非我实际上try实际上,当我导入扩展模块时,Python 解释器会立即出现段错误。好的。
好吧,现在我放弃官方 Python 文档并转向 Google。所以,这个随机博客声称您需要从扩展模块做的就是调用PyEval_InitThreads()
。当然,文档声称PyEval_InitThreads()
获得 GIL,事实上,快速检查源代码PyEval_InitThreads() in ceval.c表明它确实调用了内部函数take_gil(PyThreadState_GET());
So PyEval_InitThreads()
确实获得 GIL。我认为你绝对需要在调用后以某种方式释放 GILPyEval_InitThreads()
. But how? PyEval_ReleaseLock()
已弃用,并且PyEval_SaveThread()
只是莫名其妙的段错误。
好吧...也许由于某种目前超出我理解的原因,C 扩展模块doesn't需要释放GIL。我尝试了...并且正如预期的那样,一旦另一个线程尝试获取 GIL(使用PyGILState_Ensure),程序因死锁而挂起。所以是的...你真的调用后需要释放GILPyEval_InitThreads()
.
那么问题又来了:调用后如何释放GILPyEval_InitThreads()?
更一般地说:C 扩展模块到底需要做什么才能从工作 C 线程安全地调用 Python 代码?