Python 3中的PyEval_InitThreads:如何/何时调用它?
基本上,在什么时候应该准确调用什么以及需要什么伴随的API调用方面似乎存在 巨大的 困惑/模糊性
PyEval_InitThreads()
。不幸的是,Python的官方文档非常模糊。关于这个话题,已经有很多关于stackoverflow的问题,因此,如果将其作为副本关闭,我不会感到特别惊讶。但是请考虑一下,这个问题似乎没有确定的答案。(可悲的是,我没有快速拨号上的Guido VanRossum。)
首先,让我们在这里定义问题的范围: 我想做什么? 好吧…我想用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文档,转向Google。因此,
这个随机博客
声称您需要从扩展模块执行的所有操作是PyEval_InitThreads()
。当然,文档声称PyEval_InitThreads()
获得了GIL,实际上,
快速检查PyEval_InitThreads()
in的源代码ceval.c
表明它确实调用了内部功能。take_gil(PyThreadState_GET());
因此PyEval_InitThreads()
一定要 获得GIL。我认为那您绝对需要在致电后以某种方式释放GIL
PyEval_InitThreads()
。 但是如何?
PyEval_ReleaseLock()
已过时,并且出现PyEval_SaveThread()
了莫名其妙的段错误。
好了…所以也许出于某种原因这是目前我无法理解,一个C扩展模块 并不
需要释放GIL。我尝试了一下……并且,正如预期的那样,一旦另一个线程尝试获取GIL(使用
PyGILState_Ensure
),程序就会从死锁中挂起。是的…您 真的 需要在致电后释放GIL PyEval_InitThreads()
。
同样,问题是:
在调用后如何释放GILPyEval_InitThreads()
?
更笼统地说: 为了能够从辅助C线程安全地调用Python代码,C扩展模块到底需要做什么?
-
您的理解是正确的:调用
PyEval_InitThreads
确实可以获取GIL。在正确编写的Python /
C应用程序中,这不是问题,因为GIL将自动或手动及时解锁。如果主线程继续运行Python代码,则没有什么特别的事情,因为Python解释器将在执行了许多指令后自动放弃GIL(允许另一个线程来获取它,它将再次放弃它,依此类推)上)。另外,每当Python要调用阻塞系统调用时(例如,从网络读取或写入文件),它将在调用周围释放GIL。
这个答案的原始版本到此为止。但是还有另外一件事要考虑: 嵌入 方案。
嵌入Python时,主线程通常会初始化Python并继续执行其他与Python不相关的任务。在那种情况下,没有什么东西会 自动
释放GIL,因此必须由线程本身完成。这绝不是特定于call的调用PyEval_InitThreads
,它是在使用获取的GIL调用的所有Python /
C代码中所期望的。例如,
main()
可能包含以下代码:Py_Initialize(); PyEval_InitThreads(); Py_BEGIN_ALLOW_THREADS ... call the non-Python part of the application here ... Py_END_ALLOW_THREADS Py_Finalize();
如果您的代码是手动创建线程的,则它们需要先获取GIL,然后再进行与Python相关的 任何操作
,甚至简单到Py_INCREF
。为此,请使用以下命令:// 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);