深入理解python多线程与GIL
来源:互联网 发布:php 图片缩略图 编辑:程序博客网 时间:2024/05/20 17:26
python 多线程效率
在一台8核的CentOS上,用python 2.7.6
程序执行一段CPU密集型
的程序。
import timedef fun(n):#CPU密集型的程序 while(n>0): n -= 1start_time = time.time()fun(10000000)print('{} s'.format(time.time() - start_time))#测量程序执行时间
测量三次程序的执行时间,平均时间为0.968370994秒
。这就是一个线程执行一次fun(10000000)
所需要的时间。
下面用两个线程并行来跑这段CPU密集型
的程序。
import timeimport threadingdef fun(n): while(n>0): n -= 1start_time = time.time()t1 = threading.Thread( target=fun, args=(10000000,) )t1.start()t2 = threading.Thread( target=fun, args=(10000000,) )t2.start()t1.join()t2.join()print('{} s'.format(time.time() - start_time))
测量三次程序的执行时间,平均时间为2.150056044秒
。
为什么在8核的机器上,多线程执行时间并不比顺序执行快呢?
再做另一个实验,用下面的命令,把8核cpu中的7个核禁掉。
[xxx]# echo 0 > /sys/devices/system/cpu/cpu1/online[xxx]# echo 0 > /sys/devices/system/cpu/cpu2/online[xxx]# echo 0 > /sys/devices/system/cpu/cpu3/online[xxx]# echo 0 > /sys/devices/system/cpu/cpu4/online[xxx]# echo 0 > /sys/devices/system/cpu/cpu5/online[xxx]# echo 0 > /sys/devices/system/cpu/cpu6/online[xxx]# echo 0 > /sys/devices/system/cpu/cpu7/online
然后在运行这个多线程的程序,三次平均时间为2.533491453秒
。为什么多线程程序在多核上跑的时间只比单核快一点点呢?
这就要提到python
程序多线程的实现机制了。
Python多线程实现机制
python
的多线程机制,就是用C实现的真实系统中的线程。线程完全被操作系统控制。
python
内部创建一个线程的步骤是这样的:
- 创建一个数据结构PyThreadState,其中含有一些解释器状态
- 调用pthread创建线程
- 执行线程函数
由于python
是解释形动态语言,所以在实现线程时,需要PyThreadState
结构来保存一些信息:
- 当前的stack frame (对python代码)
- 当前的递归深度
- 线程ID
- 可选的tracing/profiling/debugging hooks
PyThreadState
是C语言实现的一个结构体(摘自[2]):
typedef struct _ts { struct _ts *next; # 链表指正 PyInterpreterState *interp; # 解释器状态 struct _frame *frame; # 当前的stack frame int recursion_depth; # 当前的递归深度 int tracing; int use_tracing; Py_tracefunc c_profilefunc; Py_tracefunc c_tracefunc; PyObject *c_profileobj; PyObject *c_traceobj; PyObject *curexc_type; PyObject *curexc_value; PyObject *curexc_traceback; PyObject *exc_type; PyObject *exc_value; PyObject *exc_traceback; PyObject *dict; int tick_counter; int gilstate_counter; PyObject *async_exc; long thread_id; # 线程ID} PyThreadState;
从目前最新的python源码中来看,这个结构体中的内容已经有所改变,但记录解释器状态的指针PyInterpreterState *interp
依然存在。
python解释器实现时,用了一个全局变量(_PyThreadState_Current
)[https://github.com/python/cpython/blob/3.1/Python/pystate.c](python3.1
和之前的代码中都存在,python3.2
就有所不同了)
PyThreadState *_PyThreadState_Current = NULL;
_PyThreadState_Current
指向当前执行线程的PyThreadState数据结构
。解释器通过这个变量,来获取当前所执行线程的信息。
python
程序中,有一个全局解释器锁GIL来控制线程的执行,每一个时刻只允许一个线程执行。
GIL的行为
GIL
最基本的行为只有下面两个:
- 当前执行的线程持有
GIL
- 线程遇到
I/O阻塞
时,会释放GIL
。(阻塞等待时,就释放GIL
,给另一个线程执行的机会)
那么,如果遇到CPU密集型
的线程,一直占用CPU
,不会被I/O
阻塞,是不是其它线程就没有机会执行了呢?
非也,为了避免这种情况,解释器还会周期性的check并执行线程调度。
解释器周期性check行为,做的就是下面这3件事:
- 复位tick计数器
- 在主线程中,检查有没有需要处理的信号
- 让当前执行线程释放(
Release
)GIL
,让其他线程获取(acquire
)GIL
并执行(给其他线程执行的机会)
而解释器check的周期,默认是100个tick。解释器的tick并不是基于时间的,每个tick大致相当于一条汇编指令的执行时间。
从解释器的check行为中可以看到,只有主线程中会处理信号,子线程中都不处理信号。所以python
多线程程序,会给人一种无法处理Ctrl+C
的假象,因为大部分情况下主线程被block住了,无法处理SIGINT信号。
注意python中并没有实现线程调度,python的多线程调度完全依赖于操作系统。所以python多线程编程中没有线程优先级等概念。
GIL的实现
python
的GIL
并不是简单的用lock
实现的,GIL
是用signal
实现的。
- 线程获取(
acquire
)GIL
前,先检查有没有被free
,如果没有,就sleep
等待signal
- 线程释放
GIL
时,还要发送signal
参考
- [1] Understanding the Python GIL. http://dabeaz.com/python/UnderstandingGIL.pdf
- [2] Inside the Python GIL. http://www.dabeaz.com/python/GIL.pdf
- 深入理解python多线程与GIL
- Python多线程与GIL
- Python 多线程 多进程 GIL
- Python, GIL, ctypes, Jython 多线程
- 理解python GIL 全局锁
- Python:线程与GIL简述
- python中多线程调度机制以及GIL
- Python中的GIL、多进程和多线程
- 谈谈python的GIL、多线程、多进程
- python中多线程中GIL的介绍
- 谈谈python的GIL、多线程、多进程
- 谈谈python的GIL、多线程、多进程
- GIL理解
- python GIL
- Python GIL
- Python GIL
- python GIL
- Python的GIL是什么鬼,多线程性能究竟如何
- Maven项目对象模型(POM)
- JZOJ 5344. 【NOIP2017模拟9.3A组】摘果子
- Eclipse 的 Java Web 项目环境搭建
- 25.Map集合
- 动态规划——求两个字符串子串的个数和最长子串的长度
- 深入理解python多线程与GIL
- Nginx动静分离+负载均衡实现
- 仅允许运行使用网络级别身份验证的远程桌面的计算机连接
- iBATIS SQL Maps之Parameter Map。
- hdu 1213 求树的个数
- ScheduledThreadPoolExecutor详解
- Selenium IDE
- Android自动登录 记住用户名密码实现 SharedPreferences
- python2.7:模块安装4种方法