0

我有一个接受请求的服务器应用程序,需要运行一些 Python,其中一些调用我的基于 C 的扩展 DLL。如果我在整个请求期间完全锁定 GIL,一切正常。但是我想在基于 C 的扩展 DLL 中释放它,当长时间操作发生时,可以处理更多请求。到目前为止,这是我的方法:

A. Request arrives on thread I don't control
B.   Call PyGILState_Ensure
C.      Call into Python runtime via PyRun_String  
D.          Python calls my MethodA in my C-based code
E.              Call PyGILState_Release 
F.                  Do long-ish processing
G.              Call PyGILState_Ensure to lock GIL again
H.          Return control to Python for further script processing
I.      Python runtime returns
J.   Call PyGILState_Release

以上所有步骤都是单线程(系统中有很多类似的线程)。

上面的调用序列总是在 Python 深处的步骤 (H) 中某处抛出异常(我似乎无法从堆栈跟踪中找出)。但是,如果我在 (B) 处对 PyGILState_Ensure 进行额外调用,以便 (E) 和 (F) 处的调用最终不会做任何事情,那么一切都会完美运行。

有人可以帮我理解我做错了什么吗?我真的很想在长时间的操作中释放 GIL,以便其他请求可以取得进展。

4

1 回答 1

2

首先,您必须匹配 B 和 E 以及 G 和 J 处的PyGILState_Ensure/PyGILState_Release对,而不是 B 和 J 以及 E 和 G 的更天真的匹配匹配。(如果你这样尝试,步骤 E 会释放一个未初始化的状态,破坏解释器的内部状态信息。)

但是您使用的是单一状态,它没有这个问题。(我实际上不确定这是否合法,但我很确定它在每个版本的 CPython 中都是安全的,而且绝对不可能让事情发生故障。)


不幸的是,这有一个不同的问题。正如PyGILState_Release文档所说,它:

释放之前获得的任何资源。在此调用之后,Python 的状态将与相应调用之前的状态相同PyGILState_Ensure()……</p>

换句话说,您不能在 aRelease和后续之间携带任何解释器资源Ensure(当然,除非它们受到外部保护Ensure,但在这种情况下,您实际上并没有释放任何东西)。

因此,一旦您Release确保了 的状态,PyRun_String该调用的其余部分PyRun_String就变得无效。在中间切换到新获得的状态并没有帮助。


但是我认为您首先是在不必要地滥用Ensure/ 。Release您不需要向解释器注册线程,注销它,再次注册它,然后再次注销它;您需要做的就是从同一个线程中释放并重新获取 GIL。这就是目的Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS目的。

正如文档PyGILState_Ensure所说:

一般来说,只要线程状态恢复到Release()之前的状态,就可以在PyGILState_Ensure()和调用之间使用其他线程相关的API。PyGILState_Release()例如,Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS宏的正常使用是可以接受的。

所以:

PyGILState_STATE state;

B. state = PyGILState_Ensure();
    ...
E.              Py_BEGIN_ALLOW_THREADS
F.                  Do long-ish processing
G.              Py_END_ALLOW_THREADS
    ...
J.  PyGILState_Release(state);
于 2013-04-17T19:42:11.963 回答