Python多线程+线程守护+GIL锁

今天开始打算开一个新系列,就是python的多线程和多进程实现,这部分可能有些新手还是比较模糊的,都知道python中的多线程是假的,但是又不知道怎么回事,首先我们看一个例子来看看python多线程的实现。




import threading

import time


def say(name):

        print('你好%s at %s' %(name,time.ctime()))

        time.sleep(2)

        print("结束%s at %s" %(name,time.ctime()))


def listen(name):

    print('你好%s at %s' % (name,time.ctime()))

    time.sleep(4)

    print("结束%s at %s" % (name,time.ctime()))


if __name__ == '__main__':

    t1 = threading.Thread(target=say,args=('tony',))  #Thread是一个类,实例化产生t1对象,这里就是创建了一个线程对象t1

    t1.start() #线程执行

    t2 = threading.Thread(target=listen, args=('simon',)) #这里就是创建了一个线程对象t2

    t2.start()


    print("程序结束=====================")



结果:


你好tony at Thu Apr 25 16:46:22 2019

你好simon at Thu Apr 25 16:46:22 2019

程序结束=====================

结束tony at Thu Apr 25 16:46:24 2019

结束simon at Thu Apr 25 16:46:26 2019


Process finished with exit code 0




python的多线程是通过threading模块的Thread类来实现的。


创建线程对象


t1 = threading.Thread(target=say,args=('tony',))  #Thread是一个类,实例化产生t1对象,这里就是创建了一个线程对象t1


启动线程


t1.start() #线程执行




下面我们分析下上面代码的结果:


你好tony at Thu Apr 25 16:46:22 2019  --t1线程执行

你好simon at Thu Apr 25 16:46:22 2019 --t2线程执行

程序结束===================== --主线程执行

结束tony at Thu Apr 25 16:46:24 2019 --sleep之后,t1线程执行

结束simon at Thu Apr 25 16:46:26 2019 --sleep之后,t2线程执行


Process finished with exit code 0 --主线程结束




我们可以看到主线程的print并不是等t1,t2线程都执行完毕之后才打印的,这是因为主线程和t1,t2 线程是同时跑的。但是主进程要等非守护子线程结束之后,主线程才会退出。




上面其实就是python多线程的最简单用法,但是可能有人会和我有一样的需求,一般开发中,我们需要主线程的print打印是在最后面的,表明所有流程都结束了,也就是主线程结束了。这里就引入了一个join的概念。





import threading

import time


def say(name):

        print('你好%s at %s' %(name,time.ctime()))

        time.sleep(2)

        print("结束%s at %s" %(name,time.ctime()))


def listen(name):

    print('你好%s at %s' % (name,time.ctime()))

    time.sleep(4)

    print("结束%s at %s" % (name,time.ctime()))


if __name__ == '__main__':

    t1 = threading.Thread(target=say,args=('tony',))  #Thread是一个类,实例化产生t1对象,这里就是创建了一个线程对象t1

    t1.start() #线程执行

    t2 = threading.Thread(target=listen, args=('simon',)) #这里就是创建了一个线程对象t2

    t2.start()


    t1.join() #join等t1子线程结束,主线程打印并且结束

    t2.join() #join等t2子线程结束,主线程打印并且结束

    print("程序结束=====================")



结果:


你好tony at Thu Apr 25 16:57:32 2019

你好simon at Thu Apr 25 16:57:32 2019

结束tony at Thu Apr 25 16:57:34 2019

结束simon at Thu Apr 25 16:57:36 2019

程序结束=====================




上面代码中加入join方法后实现了,我们上面所想要的结果,主线程print最后执行,并且主线程退出,注意主线程执行了打印操作和主线程结束不是一个概念,如果子线程不加join,则主线程也会执行打印,但是主线程不会结束,还是需要待非守护子线程结束之后,主线程才结束。




上面的情况,主进程都需要等待非守护子线程结束之后,主线程才结束。那我们是不是注意到一点,我说的是“非守护子线程”,那什么是非守护子线程?默认的子线程都是主线程的非守护子线程,但是有时候我们有需求,当主进程结束,不管子线程有没有结束,子线程都要跟随主线程一起退出,这时候我们引入一个“守护线程”的概念。




如果某个子线程设置为守护线程,主线程其实就不用管这个子线程了,当所有其他非守护线程结束,主线程就会退出,而守护线程将和主线程一起退出,守护主线程,这就是守护线程的意思




看看具体代码,我们这里分2种情况来讨论守护线程,加深大家的理解,


还有一点,这个方法一定要设置在start方法前面




1.设置t1线程为守护线程,看看执行结果




import threading

import time


def say(name):

        print('你好%s at %s' %(name,time.ctime()))

        time.sleep(2)

        print("结束%s at %s" %(name,time.ctime()))


def listen(name):

    print('你好%s at %s' % (name,time.ctime()))

    time.sleep(4)

    print("结束%s at %s" % (name,time.ctime()))


if __name__ == '__main__':

    t1 = threading.Thread(target=say,args=('tony',))  #Thread是一个类,实例化产生t1对象,这里就是创建了一个线程对象t1

    t1.setDaemon(True)

    t1.start() #线程执行

    t2 = threading.Thread(target=listen, args=('simon',)) #这里就是创建了一个线程对象t2

    t2.start()


    print("程序结束=====================")



结果:


你好tony at Thu Apr 25 17:11:41 2019

你好simon at Thu Apr 25 17:11:41 2019


程序结束=====================


结束tony at Thu Apr 25 17:11:43 2019

结束simon at Thu Apr 25 17:11:45 2019




注意执行顺序,


这里如果设置t1为Daemon,那么主线程就不管t1的运行状态,只管等待t2结束, t2结束主线程就结束了

因为t2的时间4秒,t1的时间2秒,主线程在等待t2线程结束的过程中,t1线程自己结束了,所以结果是:

你好tony at Thu Apr 25 14:11:54 2019

你好simon at Thu Apr 25 14:11:54 2019程序结束===============

结束tony at Thu Apr 25 14:11:56 2019  (也会打印,因为主线程在等待t2线程结束的过程中, t1线程自己结束了)

结束simon at Thu Apr 25 14:11:58 2019






2.设置t2为守护线程


import threading

import time


def say(name):

        print('你好%s at %s' %(name,time.ctime()))

        time.sleep(2)

        print("结束%s at %s" %(name,time.ctime()))


def listen(name):

    print('你好%s at %s' % (name,time.ctime()))

    time.sleep(4)

    print("结束%s at %s" % (name,time.ctime()))


if __name__ == '__main__':

    t1 = threading.Thread(target=say,args=('tony',))  #Thread是一个类,实例化产生t1对象,这里就是创建了一个线程对象t1

    t1.start() #线程执行

    t2 = threading.Thread(target=listen, args=('simon',)) #这里就是创建了一个线程对象t2

    t2.setDaemon(True)

    t2.start()


    print("程序结束=====================")





结果:


你好tony at Thu Apr 25 17:15:36 2019

你好simon at Thu Apr 25 17:15:36 2019


程序结束=====================


结束tony at Thu Apr 25 17:15:38 2019




注意执行顺序:


这里如果设置t2为Daemon,那么主线程就不管t2的运行状态,只管等待t1结束, t1结束主线程就结束了

因为t2的时间4秒,t1的时间2秒, 主线程在等待t1线程结束的过程中, t2线程自己结束不了,所以结果是:

你好tony at Thu Apr 25 14:14:23 2019

你好simon at Thu Apr 25 14:14:23 2019

程序结束 == == == == == == == == == == =

结束tony at Thu Apr 25 14:14:25 2019

结束simon at Thu Apr 25 14:11:58 2019 不会打印,因为主线程在等待t1线程结束的过程中, t2线程自己结束不了,t2的时间4秒,t1的时间2秒



不知道大家有没有弄清楚上面python多线程的实现方式以及join,守护线程的用法。




主要方法:




join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。


setDaemon(True):


将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。


当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成


想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程


    完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦










其他方法:




run():  线程被cpu调度后自动执行线程对象的run方法

start():启动线程活动。

isAlive(): 返回线程是否活动的。

getName(): 返回线程名。

setName(): 设置线程名。




threading模块提供的一些方法:

threading.currentThread(): 返回当前的线程变量。

threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。






上面的例子中我们注意到两如果个任务如果顺序执行要6s结束,如果是多线程执行4S结束,性能是有所提升的,但是我们要知道这里的性能提升实际上是由于cpu并发实现性能提升,也就是cpu线程切换(多道技术)带来的,而并不是真正的多cpu并行执行。




上面提到了并行和并发,那这两者有什么区别呢?


并发:是指一个系统具有处理多个任务的能力(cpu切换,多道技术)

并行:是指一个系统具有同时处理多个任务的能力(cpu同时处理多个任务)

并行是并发的一种情况,子集




那为什么python在多线程中为什么不能实现真正的并行操作呢?就是在多cpu中执行不同的线程(我们知道JAVA中多个线程可以在不同的cpu中,实现并行运行)这就要提到python中大名鼎鼎GIL,那什么是GIL?




GIL:全局解释器锁  无论你启多少个线程,你有多少个cpu, Python在执行的时候只会的在同一时刻只允许一个线程(线程之间有竞争)拿到GIL在一个cpu上运行,当线程遇到IO等待或到达者轮询时间的时候,cpu会做切换,把cpu的时间片让给其他线程执行,cpu切换需要消耗时间和资源,所以计算密集型的功能(比如加减乘除)不适合多线程,因为cpu线程切换太多,IO密集型比较适合多线程。



任务:

IO密集型(各个线程都会都各种的等待,如果有等待,线程切换是比较适合的),也可以采用可以用多进程+协程

计算密集型(线程在计算时没有等待,这时候去切换,就是无用的切换),python不太适合开发这类功能




我们前面举得例子里面模拟了sleep操作,其实就是相当于遇到IO,这种场景用多线程是可以增加性能的,但是如果我们用多线程来计算数据的计算,性能反而会降低。






下面是GIL的一段原生解释:




In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once.

This lock is necessary mainly because CPython’s memory management is not thread-safe.

(However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)





个人见解,望指教

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 一文读懂Python多线程 1、线程和进程 计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运...
    星丶雲阅读 1,442评论 0 4
  • 环境 xubuntu anaconda pycharm python https://www.cnblogs.co...
    Ericoool阅读 1,890评论 0 0
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,630评论 0 6
  • 宋武帝刘裕去世后,谢晦、徐羡之、傅亮三人受命为顾命大臣,辅佐宋少帝刘义符。但刘义符自幼娇生惯养,登基后整日与宫人游...
    寒七琪阅读 1,022评论 3 4
  • 如果落叶有痕迹 肯定是个婀娜的少女 用最曼妙的姿态 潜伏在人间。
    留子尧阅读 283评论 2 4