深入学习python多线程与GIL


Posted in Python onAugust 26, 2019

python 多线程效率

在一台8核的CentOS上,用python 2.7.6程序执行一段CPU密集型的程序。

import time
def fun(n):#CPU密集型的程序
  while(n>0):
    n -= 1

start_time = time.time()
fun(10000000)
print('{} s'.format(time.time() - start_time))#测量程序执行时间

测量三次程序的执行时间,平均时间为0.968370994秒。这就是一个线程执行一次fun(10000000)所需要的时间。

下面用两个线程并行来跑这段CPU密集型的程序。

import time
import threading

def fun(n):
  while(n>0):
    n -= 1

start_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 相关文章推荐
python爬取网站数据保存使用的方法
Nov 20 Python
Python文件夹与文件的操作实现代码
Jul 13 Python
python实现判断数组是否包含指定元素的方法
Jul 15 Python
python3+PyQt5使用数据库窗口视图
Apr 24 Python
Python使用win32com模块实现数据库表结构自动生成word表格的方法
Jul 17 Python
python pandas获取csv指定行 列的操作方法
Jul 12 Python
python利用re,bs4,requests模块获取股票数据
Jul 29 Python
使用OpenCV实现仿射变换—旋转功能
Aug 29 Python
Python hashlib加密模块常用方法解析
Dec 18 Python
tensorflow 实现数据类型转换
Feb 17 Python
Python基于数列实现购物车程序过程详解
Jun 09 Python
python实现三次密码验证的示例
Apr 29 Python
用python生成与调用cntk模型代码演示方法
Aug 26 #Python
python list转置和前后反转的例子
Aug 26 #Python
python3 map函数和filter函数详解
Aug 26 #Python
python爬虫 2019中国好声音评论爬取过程解析
Aug 26 #Python
解决Python计算矩阵乘向量,矩阵乘实数的一些小错误
Aug 26 #Python
对Python中一维向量和一维向量转置相乘的方法详解
Aug 26 #Python
python 中xpath爬虫实例详解
Aug 26 #Python
You might like
php+js iframe实现上传头像界面无跳转
2014/04/29 PHP
PHP常用技巧汇总
2016/03/04 PHP
PHP 年月日的三级联动实例代码
2017/05/24 PHP
smarty模板的使用方法实例分析
2019/09/18 PHP
给网站上的广告“加速”显示的方法
2007/04/08 Javascript
Firefox window.close()的使用注意事项
2009/04/11 Javascript
用JavaScript页面不刷新时全选择,全删除(GridView)
2009/04/14 Javascript
javascript游戏开发之《三国志曹操传》零部件开发(二)人物行走的实现
2013/01/23 Javascript
JS等比例缩小图片尺寸的实例
2013/02/27 Javascript
jquery.post用法之type设置问题
2014/02/24 Javascript
jquery如何把数组变为字符串传到服务端并处理
2014/04/30 Javascript
微信企业号开发之微信考勤百度地图定位
2015/09/11 Javascript
js如何判断访问是来自搜索引擎(蜘蛛人)还是直接访问
2015/09/14 Javascript
JS加载器如何动态加载外部js文件
2016/05/26 Javascript
Javascript将数值转换为金额格式(分隔千分位和自动增加小数点)
2016/06/22 Javascript
jQuery实现碰到边缘反弹的动画效果
2018/02/24 jQuery
es6数据变更同步到视图层的方法
2019/03/04 Javascript
JS实现可视化音频效果的实例代码
2020/01/16 Javascript
[02:51]2014DOTA2 TI小组赛总结中国军团全部进军钥匙球馆
2014/07/15 DOTA
[01:13:46]iG vs Winstrike 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
python实现自动更换ip的方法
2015/05/05 Python
Python中创建二维数组
2018/10/17 Python
如何在Django中添加没有微秒的 DateTimeField 属性详解
2019/01/30 Python
Python实现 版本号对比功能的实例代码
2019/04/18 Python
详解centos7+django+python3+mysql+阿里云部署项目全流程
2019/11/15 Python
Python基于Tensor FLow的图像处理操作详解
2020/01/15 Python
解析pip安装第三方库但PyCharm中却无法识别的问题及PyCharm安装第三方库的方法教程
2020/03/10 Python
Canon佳能美国官方商店:购买数码相机、数码单反相机、镜头和打印机
2016/11/15 全球购物
俄罗斯最大的香水和化妆品网上商店:Randewoo
2020/11/05 全球购物
面向对象设计的原则是什么
2013/02/13 面试题
教师自我鉴定范文
2013/11/10 职场文书
人事主管岗位职责范本
2013/12/04 职场文书
导师对论文的学术评语
2015/01/04 职场文书
撤诉状格式范本
2015/05/19 职场文书
学校体育节班级口号
2015/12/25 职场文书
2019班干部竞选演讲稿范本!
2019/07/08 职场文书