深入学习python多线程与GIL

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • python进阶之多线程对同一个全局变量的处理方法

    通常情况下: from threading import Thread global_num = 0 def func1(): global global_num for i in range(1000000): global_num += 1 print('---------func1:global_num=%s--------'%global_num) def func2(): global global_num for i in range(1000000): global_num +=

  • 一篇文章快速了解Python的GIL

    前言:博主在刚接触Python的时候时常听到GIL这个词,并且发现这个词经常和Python无法高效的实现多线程划上等号.本着不光要知其然,还要知其所以然的研究态度,博主搜集了各方面的资料,花了一周内几个小时的闲暇时间深入理解了下GIL,并归纳成此文,也希望读者能通过次本文更好且客观的理解GIL. GIL是什么 首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念.就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执

  • 对Python多线程读写文件加锁的实例详解

    Python的多线程在io方面比单线程还是有优势,但是在多线程开发时,少不了对文件的读写操作.在管理多个线程对同一文件的读写操作时,就少不了文件锁了. 使用fcntl 在linux下,python的标准库有现成的文件锁,来自于fcntl模块.这个模块提供了unix系统fcntl()和ioctl()的接口. 对于文件锁的操作,主要需要使用 fcntl.flock(fd, operation)这个函数. 其中,参数 fd 表示文件描述符:参数 operation 指定要进行的锁操作,该参数的取值有如

  • Python Threading 线程/互斥锁/死锁/GIL锁

    导入线程包 import threading 准备函数线程,传参数 t1 = threading.Thread(target=func,args=(args,)) 类继承线程,创建线程对象 class MyThread(threading.Thread) def run(self): pass if __name__ == "__main__": t = MyThread() t.start() 线程共享全面变量,但在共享全局变量时会出现数据错误问题 使用 threading 模块中的

  • python time.sleep()是睡眠线程还是进程

    python time.sleep()-睡眠线程还是进程? 它会阻止线程.如果查看Python源代码中的Modules / timemodule.c,您会看到在调用中floatsleep(),睡眠操作的实质部分包含在Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS块中,允许其他线程继续执行当前线程睡觉.你也可以用一个简单的python程序来测试它: import time from threading import Thread class worker(T

  • Python中GIL的使用详解

    1.GIL简介 GIL的全称为Global Interpreter Lock,全局解释器锁. 1.1 GIL设计理念与限制 python的代码执行由python虚拟机(也叫解释器主循环,CPython版本)来控制,python在设计之初就考虑到在解释器的主循环中,同时只有一个线程在运行.即在任意时刻只有一个线程在解释器中运行.对python虚拟机访问的控制由全局解释锁GIL控制,正是这个锁来控制同一时刻只有一个线程能够运行. 在调用外部代码(如C.C++扩展函数)的时候,GIL将会被锁定,直到这

  • 浅谈Python中的全局锁(GIL)问题

    CPU-bound(计算密集型) 和I/O bound(I/O密集型) 计算密集型任务(CPU-bound) 的特点是要进行大量的计算,占据着主要的任务,消耗CPU资源,一直处于满负荷状态.比如复杂的加减乘除.计算圆周率.对视频进行高清解码等等,全靠CPU的运算能力.这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数. 计算密集型任务由于主要消耗CPU资源,因

  • Python从使用线程到使用async/await的深入讲解

    前言 为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读. 请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换: 把@asyncio.rotoutine替换为async: 把yield from替换为await. async/await 是一种异步变成方法,还有两种你可能听过, 1. 回调 2. Promise (写过 JavaScript 的肯定很熟悉了) 异

  • 深入学习python多线程与GIL

    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(

  • Python 多线程抓取图片效率对比

    目的: 是学习python 多线程的工作原理,及通过抓取400张图片这种IO密集型应用来查看多线程效率对比 import requests import urlparse import os import time import threading import Queue path = '/home/lidongwei/scrapy/owan_img_urls.txt' #path = '/home/lidongwei/scrapy/cc.txt' fetch_img_save_path =

  • python多线程用法实例详解

    本文实例分析了python多线程用法.分享给大家供大家参考.具体如下: 今天在学习尝试学习python多线程的时候,突然发现自己一直对super的用法不是很清楚,所以先总结一些遇到的问题.当我尝试编写下面的代码的时候: 复制代码 代码如下: class A():     def __init__( self ):         print "A" class B( A ):     def __init__( self ):         super( B, self ).__in

  • Python多线程入门学习

    Python 中使用线程有两种方式:函数或者用类来包装线程对象. 函数式: 调用 thread 模块中的start_new_thread()函数来产生新线程. 语法如下: thread.start_new_thread(function, args[, kwargs]) 参数说明: function - 线程函数. args - 传递给线程函数的参数,它必须是个 tuple 类型. kwargs - 可选参数. import thread import time # 为线程定义一个函数 def

  • Python并发编程多进程,多线程及GIL全局解释器锁

    目录 1. 并发与并行 2. 线程与进程的应用场景 2.1. 并行/并发编程相关的技术栈 3. Python中的GIL是什么,它影响什么 1. 并发与并行 所谓的并行(Parallelism),就是多个彼此独立的任务可以同时一起执行,彼此并不相互干扰,并行强调的是同时且独立的运行,彼此不需要协作. 而所谓并发(Concurrency),则是多个任务彼此交替执行,但是同一时间只能有一个处于运行状态,并发执行强调任务之间的彼此协作. 并发通常被误解为并行,并发实际是隐式的调度独立的代码,以协作的方式

  • Python多线程爬虫简单示例

    python是支持多线程的,主要是通过thread和threading这两个模块来实现的.thread模块是比较底层的模块,threading模块是对thread做了一些包装的,可以更加方便的使用. 虽然python的多线程受GIL限制,并不是真正的多线程,但是对于I/O密集型计算还是能明显提高效率,比如说爬虫. 下面用一个实例来验证多线程的效率.代码只涉及页面获取,并没有解析出来. # -*-coding:utf-8 -*- import urllib2, time import thread

  • 详解Python多线程

    本文实例为大家解析了Python多线程,供大家参考,具体内容如下 1.多线程的理解 多进程和多线程都可以执行多个任务,线程是进程的一部分.线程的特点是线程之间可以共享内存和变量,资源消耗少(不过在Unix环境中,多进程和多线程资源调度消耗差距不明显,Unix调度较快),缺点是线程之间的同步和加锁比较麻烦. 2.Python多线程创建 在Python中,同样可以实现多线程,有两个标准模块thread和threading,不过我们主要使用更高级的threading模块.使用例子: import th

  • 详解python多线程、锁、event事件机制的简单使用

    线程和进程 1.线程共享创建它的进程的地址空间,进程有自己的地址空间 2.线程可以访问进程所有的数据,线程可以相互访问 3.线程之间的数据是独立的 4.子进程复制线程的数据 5.子进程启动后是独立的 ,父进程只能杀掉子进程,而不能进行数据交换 6.修改线程中的数据,都是会影响其他的线程,而对于进程的更改,不会影响子进程 threading.Thread Thread 是threading模块中最重要的类之一,可以使用它来创建线程.有两种方式来创建线程:一种是通过继承Thread类,重写它的run

  • python多线程同步实例教程

    前言 进程之间通信与线程同步是一个历久弥新的话题,对编程稍有了解应该都知道,但是细说又说不清.一方面除了工作中可能用的比较少,另一方面就是这些概念牵涉到的东西比较多,而且相对较深.网络编程,服务端编程,并发应用等都会涉及到.其开发和调试过程都不直观.由于同步通信机制的原理都是想通的,本文希通过望借助python实例来将抽象概念具体化. 阅读之前可以参考之前的一篇文章:python多线程与多进程及其区别,了解一下线程和进程的创建. python多线程同步 python中提供两个标准库thread和

  • python多线程与多进程及其区别详解

    前言 个人一直觉得对学习任何知识而言,概念是相当重要的.掌握了概念和原理,细节可以留给实践去推敲.掌握的关键在于理解,通过具体的实例和实际操作来感性的体会概念和原理可以起到很好的效果.本文通过一些具体的例子简单介绍一下python的多线程和多进程,后续会写一些进程通信和线程通信的一些文章. python多线程 python中提供两个标准库thread和threading用于对线程的支持,python3中已放弃对前者的支持,后者是一种更高层次封装的线程库,接下来均以后者为例. 创建线程 pytho

随机推荐