目录

1.GIL

1.1 为什么要有GIL

1.2 GIL的运作方式

1.3 GIL带来的问题

2.多线线程

2.1 线程的调度和启动

3.线程构造与使用

3.1调用Thread类构造器创建线程

3.2继承Thread类创建线程类


在学习python多线程过程,学习一些很不错的文章和教程,这里进行摘抄并总结归纳一下,便于加深自己的理解

1.GIL

GIL的全称是全局解释器锁(Global Interpreter Lock),GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++,可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

1.1 为什么要有GIL

相比于使用更细粒度的锁,GIL具有以下优点:

  1. 在单线程任务中更快;
  2. 在多线程任务中,对于I/O密集型程序运行更快;
  3. 在多线程任务中,对于用C语言包来实现CPU密集型任务的程序运行更快;
  4. 在写C扩展的时候更加容易,因为除非你在扩展中允许,否则Python解释器不会切换线程;
  5. 在打包C库时更加容易。我们不用担心线程安全性。,因为如果该库不是线程安全的,则只需在调用GIL时将其锁定即可。

1.2 GIL的运作方式

  1. 某个线程拿到GIL
  2. 该线程执行代码,直到达到了check_interval*
  3. 解释器让当前线程释放GIL
  4. 所有的线程开始竞争GIL
  5. 竞争到GIL锁的线程又从第1步开始执行

1.3 GIL带来的问题

因为有GIL的存在,由CPython做解释器(虚拟机)的多线程Python程序只能利用多核处理器的一个核来运行。

例如,我们将一个8线程的JAVA程序运行在4核的处理器上,那么每个核会运行1个线程,然后利用时间片轮转,轮流运行每一个线程。但是,我们将一个8线程的Python程序(由CPython作解释器)运行在一个4核处理器上,那么总共只会有1个核在工作,8个线程都要在这一个核上面时间片轮转。

所以说,用Python做多线程的最佳场景是处理I/O密集型应用,例如网络爬虫,因为I/O的速度比CPU的速度慢非常多,这样不同线程的上下文切换(context switch)的时间就可以忽略不计了。如果做CPU密集型任务,则使用多进程可以更好地利用CPU的多核性能。另外,使用gevent协程可以避免多线程的上下文切换的问题

在python中使用都是操作系统级别的线程,linux中使用的pthread,window使用的是其原生线程

2.多线线程

多线程的好处自不必赘述,通过一个简单的例子来看一下,参考关于播放音乐和视频

先看看单线程的

#coding=utf-8
import threading
from time import ctime,sleepdef music(func):for i in range(2):print("I was listening to %s. %s" %(func,ctime()))sleep(1)def move(func):for i in range(2):print("I was at the %s! %s" %(func,ctime()))sleep(5)if __name__ == '__main__':print("all start %s" %ctime())music(u'Music')move(u'Vedio')print("all over %s" %ctime())

输出如下:

但实际上诗就像MV一样,这两个是一般是同时开展的

#coding=utf-8
import threading
from time import ctime,sleepdef music(func):for i in range(2):print("I was listening to %s. %s" %(func,ctime()))sleep(1)def move(func):for i in range(2):print("I was at the %s! %s" %(func,ctime()))sleep(5)threads = []
t1 = threading.Thread(target=music,args=(u'Music',))
threads.append(t1)
t2 = threading.Thread(target=move,args=(u'Vedio',))
threads.append(t2)if __name__ == '__main__':print("all start %s" % ctime())for t in threads:t.setDaemon(True)t.start()t.join()print("all over %s" %ctime())

1.join()方法,用于等待线程终止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞等待子线程结束

2.程序中一般有主线程和子线程,两种线程都执行完毕,认为是程序执行结束。setDaemon(True)将线程声明为守护线程,此类线程的特点是,当程序中主线程及所有非守护线程执行结束时,未执行完毕的守护线程也会随之消亡(进行死亡状态),程序将结束运行。Python 解释器的垃圾回收机制就是守护线程的典型代表,当程序中所有主线程及非守护线程执行完毕后,垃圾回收机制也就没有再继续执行的必要了。需要注意的一点是,线程对象调用 daemon 属性必须在调用 start() 方法之前,否则 Python 解释器将报 RuntimeError 错误

参考 Python daemon守护线程详解


输入结果如下:

2.1 线程的调度和启动

python 中一个线程对应c语言中一个线程。GIL使得同一个时刻只有一个线程运行在cpu上执行字节码,无法将多个线程映射到多个cpu上执行,GIL会根据执行的字节码行数以及时间释放GIL,在遇到IO操作时会主动释放。python提供了_thread 和threading 两个模块来支持多线程,其中_thread提供低级别的,原始的线程支持,以及一个简单的锁,但是一般不建议使用_thread模块;而threading模块则提供了功能丰富的多线程支持。

通过一个对一个全局变量进行增减操作,来解释上述蓝色字体描述

total = 0
def add(name):global totalfor i in range(10000):total += 1print('当前是%s线程,i的值为%s' % (name,total))def desc(name):global totalfor i in range(10000):total -= 1print('当前是%s线程,i的值为%s' % (name,total))import threading
thread1 = threading.Thread(target=add,args=('thread1',))
thread2 = threading.Thread(target=desc,args=('thread2',))thread1.start()
thread2.start()thread1.join()
thread2.join()print(total)

输入结果如下所示:

从输出结果来看,并不是等待其中一个thread跑完了再去跑另一个线程任务

3.线程构造与使用

python 主要提供两种方式来创建线程:

  • 使用threading模块的Thread类的构造器创建线程
  • 继承 threading模块的Thread类创建线程类

3.1调用Thread类构造器创建线程

调用Thread类的构造器创建线程,直接调用threading.Thread类的如下构造器创建线程

__init__(self, group= None, target=None, name= None, args=(), kwargs= None, *, daemon= None)target :指定该线程要调度的目标方法。只传函数名,不传函数,即不加()
args :指定一个元组,以位置参数的形式为target指定的函数传入参数。元组的第一个参数传给target的第一个,以此类推。
kwargs:指定一个字典,以关键字参数的形式为target指定的函数传入参数
daemon: 指定所构建的线程是否为后台线程。

调用Thread类的构造器创建并启动多线程的步骤:

  1. 调用Thread类的构造器创建线程对象。在创建线程对象时。target参数指定的函数将作为线程的执行实体

  2. 调用线程对象的start()方法启动线程。

import threading
#定义一个普通action方法,该方法准备作为线程的执行实体
def action(max):for i in range(max):#调用threading模块的current_thread()函数获取当前线程#调用线程对象的getName()方法获取单前线程的名字print(threading.current_thread().getName() + " " +str(i))
#下面是主程序
for i in range(100):#调用threading模块的current_thread()函数获取当前线程print(threading.current_thread().getName() + " " +str(i))if  i == 20:t1 = threading.Thread(target=action, args=(100,))t1.start()t2 = threading.Thread(target =action, args=(100,))t2.start()
print("主线程执行完成!")

执行结果如下:

可以看到在循环变量达到20时,创建并且启动了两个新线程。所以此时一共有三个线程,但是三个线程之间的执行没有没有先后顺序,他们的执行方式为Thread-1执行一段时间Thread-2或MainThread获得CPU执行一段时间。

  • threading.current_thread():它是threading模块的函数,该函数总是返回当前正在执行的线程对象
  • getName() 它是Thread类的实例方法,该方法返回调用它的线程名字
    -setName() 方法可以为线程设置名字,getName和setName 两个方法可通过name属性来代替。

3.2继承Thread类创建线程类

  • 定义Thread类的子类,并重写该类的run()方法。run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。

  • 创建Thread子类的实例,即创建线程对象

  • 调用线程对象的start()方法来启动线程

import threading
import timeclass GetDetailHtml(threading.Thread):def __init__(self, name):super().__init__(name=name)def run(self):print("get detail html started")time.sleep(2)print("get detail html end")class GetDetailUrl(threading.Thread):def __init__(self, name):super().__init__(name=name)def run(self):print("get detail url started")time.sleep(2)print("get detail url end")if __name__ == "__main__":thread1 = GetDetailHtml("get_detail_html")thread2 = GetDetailUrl("get_detail_url")start_time = time.time()thread1.start()thread2.start()"""join进行线程阻塞,只有两个线程都执行完成后才执行后边的"""thread1.join()thread2.join()print("last time :{}".format(time.time() - start_time))

输出结果如下:

参考 python中的GIL详解

参考 python 多线程

参考 python并发编程

python多线程与GIL相关推荐

  1. 再谈Python多线程--避免GIL对性能的影响

    在Python中使用多线程,如果你对GIL本身没有一定的了解:那么很有可能你只是写出了正确的多线程代码,而并没有达到多线程的目的,甚至截然相反的效果.下面介绍了Python中GIL的作用和局限性,并提 ...

  2. c语言多线程转python多线程,真正的python 多线程!一个修饰符让你的多线程和C语言一样快...

    > Python 多线程因为GIL的存在,导致其速度比单线程还要慢.但是近期我发现了一个相当好用的库,这个库只需要增加一个修饰符就可以使原生的python多线程实现真正意义上的并发.本文将和大家 ...

  3. Python 多线程+多进程简单使用教程,如何在多进程开多线程

    一.Python多进程多线程 关于python多进程多线程的相关基础知识,在我之前的博客有写过,并且就关于python多线程的GIL锁问题,也在我的一篇博客中有相关的解释. 为什么python多线程在 ...

  4. 【python第三方库】python多线程编程---threading库

    文章目录 一.python多线程 1. GIL 二.threading库使用介绍 1. 创建多线程 2. 线程合并 3. 线程同步与互斥锁Lock 4. 死锁与可重入锁(递归锁)RLock 5. 守护 ...

  5. python多线程为啥是假的?(GIL 全局解释器锁)(python多线程不适合并行化的计算密集型代码)

    1. 问: 答: 2. from threading import Thread ​ def loop(): ​while True: ​print("亲爱的,我错了,我能吃饭了吗?&quo ...

  6. python多线程gil_Python 多线程、多进程 (一)之 源码执行流程、GIL

    一.python程序的运行原理 许多时候,在执行一个python文件的时候,会发现在同一目录下会出现一个__pyc__文件夹(python3)或者.pyc后缀(python2)的文件 Python在执 ...

  7. python多线程执行同样代码_Python 多线程、多进程 (一)之 源码执行流程、GIL

    一.python程序的运行原理 许多时候,在执行一个python文件的时候,会发现在同一目录下会出现一个__pyc__文件夹(python3)或者.pyc后缀(python2)的文件 Python在执 ...

  8. python3 多线程_图解|为什么 Python 多线程无法利用多核

    (给Python开发者加星标,提升Python技能) 来源:后端技术指南针 1.全局解释锁 如题: Python的多线程为什么不能利用多核处理器? 全局解释器锁(Global Interpreter ...

  9. python 多线程和协程结合_一文讲透 “进程、线程、协程”

    本文从操作系统原理出发结合代码实践讲解了以下内容: 什么是进程,线程和协程? 它们之间的关系是什么? 为什么说Python中的多线程是伪多线程? 不同的应用场景该如何选择技术方案? ... 什么是进程 ...

最新文章

  1. 算法实现太难了?机器学习也需要开源软件
  2. 【转】文件恢复神器extundelete
  3. Spring框架中的设计模式(三)
  4. Oracle备份时发现空间不够,存储空间比较紧张的情况下,如何提升Oracle备份的去重率?...
  5. Ubuntu14.04 x64系统设置Qt5.8桌面快捷方式
  6. 2019-03-14-算法-进化(移动零)
  7. android开机logo制作工具,Android 开机Logo制作
  8. linux ps2键盘驱动,通用键盘鼠标模拟(包括USB和PS2)
  9. python内置模块重要程度排名_python常用内置模块
  10. python算法学习代码_python 算法学习部分代码记录篇章1
  11. go语言:一些环境变量
  12. Servlet→对象监听器、事件监听器、Session钝化活化、@WebListener标注、过滤器概念原理生命周期、过滤器链、@WebFilter标注、定时器Timer、cancel()、purge
  13. 【笔记】android应用签名
  14. dos盘启动计算机,u盘制作dos启动盘方法
  15. 英语不好、数学不好能自学编程吗?自学编程出路如何?老程告诉你
  16. MPB:南农韦中组-​根际细菌群落资源利用网络的研究方法
  17. Net-Speeder为OpenVZ加速
  18. Problem E: Pineapple
  19. matlab的fft与ifft,fft与ifft区别
  20. 启发式算法的基础定义与了解

热门文章

  1. Arcface demo
  2. Loudrunner常用函数
  3. Android中利用Jsoup让WebView清除Html标签并让图片适应大小并居中
  4. logging 模块
  5. php or || 和 and
  6. Android 程序适应多种多分辨率
  7. flash player10.1 + FMS4中的p2p功能
  8. 动态链接库dll,静态链接库lib, 导入库lib 转
  9. 【转】C#正则表达式小结
  10. 如何修改root目录内容后打包