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