基本上,在什么时候应该准确调用什么以及需要什么伴随的API调用方面似乎存在 巨大的 困惑/模糊性 PyEval_InitThreads() 。不幸的是,Python的官方文档非常模糊。关于这个话题,已经有很多关于stackoverflow的问题,因此,如果将其作为副本关闭,我不会感到特别惊讶。但是请考虑一下,这个问题似乎没有确定的答案。(可悲的是,我没有快速拨号上的Guido VanRossum。)
PyEval_InitThreads()
首先,让我们在这里定义问题的范围: 我想做什么? 好吧…我想用C写一个Python扩展模块,它将:
pthread
好的,让我们从Python文档本身开始。在 Python的3.2文档 说:
无效PyEval_InitThreads() 初始化并获取全局解释器锁。在创建第二个线程或进行任何其他线程操作(例如PyEval_ReleaseThread(tstate))之前,应在主线程中调用它。在调用PyEval_SaveThread()或PyEval_RestoreThread()之前不需要它。
无效PyEval_InitThreads()
初始化并获取全局解释器锁。在创建第二个线程或进行任何其他线程操作(例如PyEval_ReleaseThread(tstate))之前,应在主线程中调用它。在调用PyEval_SaveThread()或PyEval_RestoreThread()之前不需要它。
所以我的理解是:
PyEval_InitThreads
因此,常识告诉我们,任何创建线程的C扩展模块都必须调用PyEval_InitThreads(),然后释放全局解释器锁。好吧,看起来很简单。因此, 表面看来 ,所需的只是以下代码:
PyEval_InitThreads(); /* initialize threading and acquire GIL */ PyEval_ReleaseLock(); /* Release GIL */
似乎很容易…但是不幸的是,Python 3.2文档 也 说它 PyEval_ReleaseLock 已被 弃用 。相反,我们应该使用 PyEval_SaveThread 它来发布GIL:
PyEval_ReleaseLock
PyEval_SaveThread
PyThreadState * PyEval_SaveThread() 释放全局解释器锁(如果已创建并启用了线程支持),然后将线程状态重置为NULL,返回先前的线程状态(非NULL)。如果已创建锁,则当前线程必须已获取它。
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());
ceval.c
take_gil(PyThreadState_GET());
因此PyEval_InitThreads() 一定要 获得GIL。我认为那您绝对需要在致电后以某种方式释放GIL PyEval_InitThreads()。 但是如何? PyEval_ReleaseLock()已过时,并且出现PyEval_SaveThread()了莫名其妙的段错误。
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()可能包含以下代码:
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。为此,请使用以下命令:
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);