深入学习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使用正则表达式提取网页URL的方法
May 26 Python
Python使用minidom读写xml的方法
Jun 03 Python
SQLite3中文编码 Python的实现
Jan 11 Python
Python实现朴素贝叶斯分类器的方法详解
Jul 04 Python
Python3+django2.0+apache2+ubuntu14部署网站上线的方法
Jul 07 Python
解决Python3中的中文字符编码的问题
Jul 18 Python
python实现单链表中删除倒数第K个节点的方法
Sep 28 Python
python3实现网络爬虫之BeautifulSoup使用详解
Dec 19 Python
Python图像处理之图像的读取、显示与保存操作【测试可用】
Jan 04 Python
基于Python实现签到脚本过程解析
Oct 25 Python
Django实现简单网页弹出警告代码
Nov 15 Python
python requests包的request()函数中的参数-params和data的区别介绍
May 05 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 不同编码下的字符串长度区分
2009/09/26 PHP
eAccelerator的安装与使用详解
2013/06/13 PHP
ThinkPHP中RBAC类的四种用法分析
2014/11/24 PHP
浅谈PHP的排列组合(如输入a,b,c 输出他们的全部组合)
2017/03/14 PHP
PHP下载大文件失败并限制下载速度的实例代码
2019/05/10 PHP
php使用redis的几种常见操作方式和用法示例
2020/02/20 PHP
javascript比较文档位置
2008/04/08 Javascript
ASP中Sub和Function的区别说明
2020/08/30 Javascript
javascript 利用Image对象实现的埋点(某处的点击数)统计
2012/12/28 Javascript
解析js如何获取当前url中的参数值并复制给input
2013/06/23 Javascript
CSS鼠标响应事件经过、移动、点击示例介绍
2013/09/04 Javascript
IE下window.onresize 多次调用与死循环bug处理方法介绍
2013/11/12 Javascript
jQuery实现首页顶部可伸缩广告特效代码
2015/04/15 Javascript
基于javascript实现数字英文验证码
2017/01/25 Javascript
JS实现仿UC浏览器前进后退效果的实例代码
2017/07/17 Javascript
详解Vue学习笔记进阶篇之列表过渡及其他
2017/07/17 Javascript
只有 20 行的 JavaScript 模板引擎实例详解
2020/05/11 Javascript
JavaScript中作用域链的概念及用途讲解
2020/08/06 Javascript
解决Vue大括号字符换行踩的坑
2020/11/09 Javascript
[01:08]DOTA2次级职业联赛 - Shield战队宣传片
2014/12/01 DOTA
Python内置函数 next的具体使用方法
2017/11/24 Python
python实现爬取百度图片的方法示例
2019/07/06 Python
python基于json文件实现的gearman任务自动重启代码实例
2019/08/13 Python
Python文件操作基础流程解析
2020/03/19 Python
英国最大的奢侈珠宝和手表网站:C W Sellors
2017/02/10 全球购物
科茨沃尔德家居商店:Scotts of Stow
2018/06/29 全球购物
Shop Apotheke瑞士:您的健康与美容网上商店
2019/10/09 全球购物
JD Sports丹麦:英国领先的运动时尚零售商
2020/11/24 全球购物
工作室成员个人发展规划范文
2014/01/24 职场文书
节能减排倡议书
2014/04/15 职场文书
大学新闻系自荐书
2014/05/31 职场文书
中秋客户感谢信
2015/01/22 职场文书
人事局接收函
2015/01/30 职场文书
2015年学校安全工作总结
2015/04/22 职场文书
公司聚餐通知
2015/04/22 职场文书
使用vuex-persistedstate本地存储vuex
2022/04/29 Vue.js