在扩展模块中对python的GIL进行解锁
在多线程环境下,python虚拟机按照以下方式运行:
1. 设置GIL(全局解释器锁)
2. 切换到一个线程中去
3. 运行
a. 指定数量的字节码的指令,或者
b. 线程主动让出控制(可以条用time.sleep(0) )
4. 把线程设置为睡眠模式
5. 解锁GIL
6. 再次重复以上步骤
调用C/C++扩展函数的时候,GIL会被锁定,直到这个函数结束。由于这期间没有python的字节码被运行,所以线程不会切换。
如果我在扩展模块中调用阻塞式地I/O函数,那么就会发生比较糟糕的事情。
比如说,我在扩展模块中接受网络数据,如果对端机器没有发送数据,那么我的整个Python程序就会阻塞到这一点,而且不会进行线程的切换。
如果发生这样的事情,python中的多线程机制就形同虚设了。
不过,python允许扩展模块自己对GIL进行解锁,方法如下:
解锁GIL的代码结构
由于这个步骤十分通用,Python提供了宏以简化代码,如下:
Py_BEGIN_ALLOW_THREADS
... Do some blocking I/O operation ...
Py_END_ALLOW_THREADS
... Do some blocking I/O operation ...
Py_END_ALLOW_THREADS
例如,如果我们在代码中使用了 getch 这个函数,那么我们的代码可以这么写:
# getch test
Py_BEGIN_ALLOW_THREADS
int key = getch();
Py_END_ALLOW_THREADS
Py_BEGIN_ALLOW_THREADS
int key = getch();
Py_END_ALLOW_THREADS
这段代码会被扩展为如下:
# getch test
PyThreadState *_save;
_save = PyEval_SaveThread();
int key = getch();
PyEval_RestoreThread(_save);
_save = PyEval_SaveThread();
int key = getch();
PyEval_RestoreThread(_save);