• Python 多线程之threading介绍



    活动地址:CSDN21天学习挑战赛

    一、为什么要有多任务?

    平时唱歌跳舞都是一起进行的,如果先唱歌后跳舞,或者先跳舞后唱歌,感觉很不协调,别扭—所以需要一个多任务
    案例:
    在这里插入图片描述

    from time import sleep
    
    def sing():
        for i in range(3):
            print("正在唱歌....%d"%i)
            sleep(1)
    def dance():
        for i in range(3):
            print("正在跳舞....%d"%i)
            sleep(1)
    if __name__ == '__main__':
        sing()
        dance()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    二、多任务介绍

    概念:多任务就是可以一边听歌,一边上网冲浪,或者在干点其它什么的,可以同时有2个以上的任务在执行
    注意:
    并发:某个时间段内,多个任务交替执行。当有多个线程在操作时,把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态(暂停)。
    并行:同一时间处理多任务的能力,多有多个线程在操作时,CPU同时处理这些线程请求的能力。

    三、threading介绍

    Python的thread模块是比较底层的,Python的threading模块是对thread做了包装,使用更加方便。

    threading常用方法:

    方法名解释
    threading.active_count()返回当前处于active状态的Thread对象
    threading.current_thread()返回当前Thread对象
    threading.get_ident()返回当前线程的线程标识符,线程标识符是一个非负整数,并无特殊含义,知识用来标识线程,该证书可能会被循环利用。Python3.3版本后支持该方法
    threading.enumerate()返回当前处于active解释器的线程对象。Python3.4版本以后支持该方法
    threading.main_thread()返回主线程对象,即启动Python解释器的线程对象。Python3.4版本以后支持该方法
    threading.stack_size()返回创建线程时使用的栈的大小,如果指定size参数,则用来指定后续创建的线程使用的栈大小,size必须是0(标识使用系统默认值)或大于32K正整数

    1、Thread类使用说明

    threading模块提供了Thread、Lock、RLock、Conditon、Event、Timer和Semaphore等类来支持多线程,Thread是其中最重要也是最基本的一个类,通过该类创建线程并控制线程的运行。

    使用Thread创建线程的方法:

    • 为构造函数传递一个可调用对象
    • 集成Thread类并在子类中重写__init__()和run()方法
      语法格式:threading.Thread(group=None,target=None,name=None,args=(),kwargs={},*,daemon=None)
      参数说明
    • group:通常默认即可,作为日后扩展ThreadGroup类实现而保留。
    • target:用于run()方法调用的可调用对象,默认为None
    • name:线程名,默认是Thread-N格式构成唯一名称,N是十进制数(正整数)
    • args:用于调用目标函数的关键字参数字典,默认为{}
    • daemon:设置线程是否为守护模式,默认None
      threading.Thread的方法和属性
      |方法名 | 解释 |
      |–|–|
      | start() | 启动线程 |
      |run()|线程代码,用来实现线程的功能与业务逻辑,可以在子类中重写该方法来自定义线程的行为|
      |init(self,group=None,target=None,name=None,args=(),kwargs=None,daemon=None)|构造函数|
      |is_alive()|判断线程是否存活|
      |getName()|返回线程名|
      |setName()|设置线程名|
      |isDaemon()|判断线程是否为守护线程|
      |setDaemon()|设置线程是否为守护线程|
      |name|用来读取或设置线程的名字|
      |ident|线程标识,用非0数字或None(线程未被启动)|
      |daemon|线程是否为守护线程,默认false|
      |join(timeout=None)|当timeout=None时,会等待至线程结束;当非None时,会等待timeout时间结束,单位秒|

    2、实例化threading.Thread(重点)

    1)单线程执行 --时间间隔

    在这里插入图片描述

    from time import sleep
    import datetime
    def sing():
        print("正在唱歌....")
        sleep(1)
    
    if __name__ == '__main__':
        for i in range(3):
            sing()
            print(datetime.datetime.now())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2)使用threading模块

    在这里插入图片描述

    import threading
    from time import sleep
    import datetime
    def sing():
        print("正在唱歌....")
        sleep(1)
    
    if __name__ == '__main__':
        for i in range(3):
            t = threading.Thread(target=sing)
            t.start()
            print(datetime.datetime.now())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 看执行速度能对比出来threading多线程速度飞快
    • 调用start()才会创建真正的多线程,开始执行代码

    3)主线程等待所有子线程结束后才结束

    在这里插入图片描述

    import threading
    from time import sleep, ctime
    
    def sing():
        for i in range(3):
            print("正在唱歌....%d" % i)
            sleep(1)
    
    def dance():
        for i in range(3):
            print("正在跳舞....%d" % i)
            sleep(1)
    
    if __name__ == '__main__':
        print("开始:", ctime())
        t1 = threading.Thread(target=sing)
        t2 = threading.Thread(target=dance)
        t1.start()
        t2.start()
        print("结束: ", ctime())
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4)查看线程数

    在这里插入图片描述

    import threading
    from time import sleep, ctime
    def sing():
        for i in range(3):
            print("正在唱歌....%d" % i)
            sleep(1)
    def dance():
        for i in range(3):
            print("正在跳舞....%d" % i)
            sleep(1)
    if __name__ == '__main__':
        print("开始:", ctime())
        t1 = threading.Thread(target=sing)
        t2 = threading.Thread(target=dance)
        t1.start()
        t2.start()
        while True:
            num = len(threading.enumerate())
            print("线程数: %d" % num)
            if (num) < 1:
                break
            sleep(1)
        print("结束: ", ctime())
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    四、继承threading.Thread

    1、线程执行封装代码

    使用threading线程模块封装时,定义一个新的子类class,继承threading.Thread,重写run方法
    在这里插入图片描述

    import threading
    from time import sleep
    
    class MyClass(threading.Thread):
        def run(self):
            for i in range(3):
                sleep(1)
                msg = self.name + "--->" + str(i)
                print(msg)
    
    
    if __name__ == '__main__':
        t = MyClass()
        t.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Python的threading.Thread类有一个run方法,定义线程功能函数,可以覆盖该方法,创建自己的线程实例后,调用start启动,交给虚拟机进行调度,有执行机会就会调用run方法执行线程。

    2、线程执行顺序

    在这里插入图片描述

    import threading
    from time import sleep
    
    class MyClass(threading.Thread):
        def run(self):
            for i in range(3):
                sleep(1)
                msg = self.name + "--->" + str(i)
                print(msg+'\n')
    
    def test():
        for i in range(5):
            t = MyClass()
            t.start()
    
    if __name__ == '__main__':
        test()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    多线程执行顺序是不确定的,sleep时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度。线程调度选择一个线程执行。以上代码能保证运行每个线程都运行完整的run函数,但是线程启动顺序、run函数每次循环执行的顺序不能确定。

    总结:

    • 每个线程默认有一个名字
    • 当线程run方法结束时,该线程完成。
    • 无法控制线程调度顺序,可以通过其它办法影响线程调度方式。

    五、多线程共享全局变量(重点)

    在这里插入图片描述

    from threading import Thread
    from time import sleep
    
    gl_num = 10
    
    def sing():
        global gl_num
        for i in range(3):
            gl_num += 1
    
        print("sing------>%d"%gl_num)
    
    def dance():
        global gl_num
        print("sing------>%d" % gl_num)
    
    t1 = Thread(target=sing)
    t1.start()
    sleep(1)
    
    t2 = Thread(target=dance)
    t2.start()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    列表当做参数传递:
    在这里插入图片描述

    from threading import Thread
    from time import sleep
    
    gl_num = [10, 20, 30]
    
    def sing(num):
        num.append(33)
        print("sing------>", num)
    
    
    def dance(num):
        sleep(1)
        print("sing------>", num)
    
    
    t1 = Thread(target=sing, args=(gl_num,))
    t1.start()
    
    t2 = Thread(target=dance, args=(gl_num,))
    t2.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 同进程内的所有线程共享全局变量,方便多个线程共享数据
    • 缺点,线程一单被修改造成多线程之间全局变量混乱现象(使用时保证全局变量不能被修改,线程非安全的)

    六、线程同步和锁(重点)

    1、线程同步

    • 概念:协同步调,按预定的先后顺序执行,多线程修改全局变量,可能会出现意外结果,为了保证数据的正确性,需要对多个线程进行同步。
    • 方法:使用Thread对象的Lock和Rlock实现简单线程同步,acquire和release方法对只允许一个线程操作的数据,可以将其放到acquire和release之间。

    2、互斥锁

    某个线程需要修改共享数据时,先将其锁定,此时资源状态为锁定,其它线程不允许修改;直到该线程释放资源,将资源状态调整非锁定,其它线程才能再次锁定该共享数据。互斥锁保证每次只有一个线程进行数据修改,能够保证多线程数据正确性。

    • threading模块中定义Lock类,方便的处理锁:
    import threading
    
    # 创建锁
    mutex = threading.Lock()
    # 锁定
    mutex.acquire()
    # 解锁
    mutex.release()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    调用acquire之前没有锁,不会堵塞;已锁,会堵塞,直到这个锁解锁

    案例:互斥锁两个线程对统一变量累加10万次
    在这里插入图片描述

    import threading
    from time import sleep
    
    gl = 0
    mutex = threading.Lock()
    
    
    def sing(num):
        global gl
        for i in range(num):
            mutex.acquire()
            gl += 1
            mutex.release()
    
        print("唱歌-----> %d" % gl)
    
    
    def dance(num):
        global gl
        for i in range(num):
            mutex.acquire()
            gl += 1
            mutex.release()
        print("跳舞------>%d" % gl)
    
    
    t1 = threading.Thread(target=sing, args=(100000,))
    t1.start()
    t2 = threading.Thread(target=dance, args=(100000,))
    t2.start()
    while len(threading.enumerate()) != 1:
        sleep(1)
    print("最终结果: %d" % gl)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    锁的拆解过程:

    • 当一个线程调用acquire()方法时锁定,状态是locked
      -只允许有一个线程获得锁,另一个线程想使用,该线程变成blocked状态,阻塞,直至有锁的线程调用release()方法释放锁,此时变成unlocked状态。
      -线程调度从阻塞线程中选择一个来调用锁,并使此线程进入运行running状态。

    总结:

    锁好处:确保了代码可以由某一线程从开始到结束独立运行
    锁坏处:阻止了多线程并发执行,单线程执行效率低下;使用多个锁可能会造成死锁

    3、死锁

    解释:线程间共享多个资源时,两个线程并各站一部分资源,同时等待对方释放资源,会造成死锁。

    如何避免死锁:

    • 程序设计时尽量避免
    • 添加超时时间等
  • 相关阅读:
    47、Dynamic View Synthesis from Dynamic Monocular Video
    使用第三方账号认证(一):钉钉扫码登录
    JSEncrypt 库非对称公私钥加解密
    [iOS]App Store Connect添加银行卡时的CNAPS代码查询
    openXBOW的使用(2)
    springboot大学新生小助手小程序毕业设计-附源码060917
    SRC漏洞挖掘信息收集与挖掘技巧
    2022最新IDEA配置Maven及Tomcat--详细、简单,适合初学者
    while语句使用
    网络中的一些基本概念
  • 原文地址:https://blog.csdn.net/walykyy/article/details/126218089