『Python CoolBook』C扩展库_其六_线程

GIL操作

想让C扩展代码和Python解释器中的其他进程一起正确的执行, 那么你就需要去释放并重新获取全局解释器锁(GIL)。

在Python接口封装中去释放并重新获取全局解释器锁(GIL),此时本段程序失去GIL运行,其他线程可以无视本函数的运行而运行,直到Py_END_ALLOW_THREADS:

#include "Python.h"
...

PyObject *pyfunc(PyObject *self, PyObject *args) {
   ...
   Py_BEGIN_ALLOW_THREADS
   // Threaded C code.  Must not use Python API functions
   ...
   Py_END_ALLOW_THREADS
   ...
   return result;
}

只有当你确保没有Python C API函数在C中执行的时候你才能安全的释放GIL。 GIL需要被释放的常见的场景是在计算密集型代码中需要在C数组上执行计算(比如在numpy中) 或者是要执行阻塞的I/O操作时(比如在一个文件描述符上读取或写入时)。

当GIL被释放后,其他Python线程才被允许在解释器中执行。 Py_END_ALLOW_THREADS 宏会阻塞执行直到调用线程重新获取了GIL。

C和Python中的线程混用

混合使用C、Python和线程, 有些线程是在C中创建的,超出了Python解释器的控制范围, 并且一些线程还使用了Python C API中的函数时,需要确保正确的初始化和管理Python的全局解释器锁(GIL)。

要想这样,可以将下列代码放到你的C代码中并确保它在任何线程被创建之前被调用:

#include <Python.h>
  ...
  if (!PyEval_ThreadsInitialized()) {
    PyEval_InitThreads();
  }
  ...

对于任何调用Python对象或Python C API的C代码,确保你首先已经正确地获取和释放了GIL, 这可以用 PyGILState_Ensure()PyGILState_Release() 来做到,如下所示:

...
/* Make sure we own the GIL */
PyGILState_STATE state = PyGILState_Ensure();

/* Use functions in the interpreter */
...
/* Restore previous GIL state and return */
PyGILState_Release(state);
...

在涉及到C和Python的高级程序中,很多事情一起做是很常见的—— 可能是对C、Python、C线程、Python线程的混合使用。 只要你确保解释器被正确的初始化,并且涉及到解释器的C代码执行了正确的GIL管理,应该没什么问题。

要注意的是调用 PyGILState_Ensure() 并不会立刻抢占或中断解释器。 如果有其他代码正在执行,这个函数被中断知道那个执行代码释放掉GIL。 在内部,解释器会执行周期性的线程切换,因此如果其他线程在执行, 调用者最终还是可以运行的(尽管可能要先等一会)。

 

posted @ 2018-05-26 17:29  叠加态的猫  阅读(575)  评论(0编辑  收藏  举报