编程语言
首页 > 编程语言> > Python 3中的PyEval_InitThreads:如何/何时调用它? (传奇继续令人作呕)

Python 3中的PyEval_InitThreads:如何/何时调用它? (传奇继续令人作呕)

作者:互联网

基本上,当应该调用PyEval_InitThreads()时,似乎存在大量的混淆/模糊,并且需要伴随API调用的内容.不幸的是,official Python documentation很暧昧.关于这个话题已经有many questions on stackoverflow了,事实上,我个人已经已经asked a question almost identical了,所以如果将其作为副本关闭,我不会特别感到惊讶;但是考虑到这个问题似乎没有明确的答案. (可悲的是,我没有Guido Van Rossum的快速拨号.)

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

>使用C中的pthread API生成工作线程
>从这些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().

所以我的理解是:

>产生线程的任何C扩展模块都必须调用
PyEval_InitThreads()来自任何其他线程之前的主线程
产生了
>调用PyEval_InitThreads会锁定GIL

所以常识告诉我们,任何创建线程的C扩展模块都必须调用PyEval_InitThreads(),然后释放Global Interpreter Lock.好吧,看起来很简单.表面上看,所需要的只是以下代码:

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();

实际上,这正是070​​07所说的.除非我在实践中尝试这样做,否则当我导入扩展模块时,Python解释器会立即出现错误.尼斯.

好的,现在我放弃官方的Python文档并转向谷歌.因此,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-extension模块究竟需要做什么才能从工作者C线程安全地调用Python代码?

解决方法:

您的理解是正确的:调用PyEval_InitThreads除其他外,还会获取GIL.在正确编写的Python / C应用程序中,这不是问题,因为GIL将自动或手动解锁.

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

这个答案的原始版本在这里结束了.但还有一件事需要考虑:嵌入场景.

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

例如,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-x,python-c-extension,python-3-2,python-c-api
来源: https://codeday.me/bug/20190917/1809864.html