• 【21天学习挑战赛】Python学习第四篇:多线程 threading 模块


    【21天学习挑战赛】Python学习第四篇:多线程 threading 模块

    ——

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

    ——

    多线程的理解就是两件或两件以上的事情通过代码同时发生。
    而一般情况下我们写python代码的话是从上往下执行的,有一个先后顺序。

    比如执行一下两行代码,就是先打印123 ,再打印456,而不是同时执行

    print('123')
    print('456')
    
    • 1
    • 2

    而现在要学习实现的多线程就是要能够多个任务同时进行。

    我们使用的 cpu 就可以执行多任务, 单核CPU 是操作系统轮流让各个任务交替执行, 任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务1执行0.01秒, 这样反复执行下去。 表面上看,每个任务都是交替执行的, 但由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

    如今大多使用多核CPU,在 任务数量远远多于CPU的核心数量情况下, 操作系统会自动把很多任务轮流调度到每个核心上执行。

    两个名词概念:
    并发
    不是真正意义上的同时执行, 是任务数比cpu核数多,通过操作系统的各种任务调度算法,像前面讲的轮流执行,实现用多个任务“一起”执行,(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
    并行
    任务数小于等于cpu核数,所有任务真的一起执行。

    ——

    threading模块的使用

    Python的 Thread 模块是比较底层的模块,python的 threading 模块是对 Thread 做了一些包装的,可以更加方便的被使用

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

    ——

    thread 类

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

    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 时,会等待至线程结束;当 timeout 不为 None 时,会等待至 timeout 时间结束,单位为秒。

    使用thread 创建线程

    语法格式:
    threading.Thread(group=None,target=None,name=None,args=(),kwargs={},*,daemon=Nome)

    参数:
    group: 通常默认即可,作为日后扩展 ThreadGroup 类实现而保留。
    target: 用于 run() 方法调用的可调用对象,默认为 None。
    name: 线程名称,默认是 Thread-N 格式构成的唯一名称,其中 N 是十进制数。
    args: 用于调用目标函数的参数元组,默认为 ()。
    kwargs: 用于调用目标函数的关键字参数字典,默认为 {}。
    daemon: 设置线程是否为守护模式,默认为 None。

    threading:Thread 的方法和属性
    start(): 启动线程。

    ——

    实例化 threading.Thread

    正常单线程:

    import threading
    import time
    
    def printt():
        print("正在打印-")
        time.sleep(1)
    
    if __name__ == "__main__":
        for i in range(5):
            printt()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行五次printt() 函数,过一秒打印一次,打印五次需要五秒。
    请添加图片描述

    ——

    使用threading模块

    def printt():
        print("正在打印-")
        time.sleep(1)
    
    if __name__ == "__main__":
        for i in range(5):
            t = threading.Thread(target=printt)
            t.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    调用start()时,才会真正的创建线程,并且开始执行
    五条打印会同时出现。
    请添加图片描述

    主线程会等待所有子线程结合后才结束

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

    两条两条打印,执行了第一个双线程,然后就打印"结束",再去执行线程中的循环打印。
    请添加图片描述

    实时线程数量查看

    import threading
    import time
    
    def sing():
        for i in range(3):
            print("正在sing%d"%i)
            time.sleep(1)
    
    def dance():
        for i in range(3):
            print("正在dance%d"%i)
            time.sleep(1)
    
    if __name__ == "__main__":
        t1 = threading.Thread(target=sing)
        t2 = threading.Thread(target=dance)
        t1.start()
        t2.start()
        
        while True:
            length = len(threading.enumerate())
            print("当前运行线程数:%d"%length)
            if length<=1:
                break
            time.sleep(1)
    
    • 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

    运行过程:
    请添加图片描述

    ——

    继承 threading.Thread

    使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承 threading.Thread 就可以了,然后重写run方法

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

    threading.Thread 类有一个 run 方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过 Thread 类的 start 方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用 run 方法执行线程。
    请添加图片描述

    线程执行顺序查看

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

    可以每五个同时输出,并且 多线程程序的执行顺序是不确定的。
    代码中只能保证每个线程都运行完整个run函数,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定。
    当线程的**run()**方法结束时该线程完成。
    请添加图片描述

    ——

    互斥锁和死锁

    应该是在"数据结构"这门课程有。

    互斥锁

    当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

    某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

    优点:

    确保了某段关键代码只能由一个线程从头到尾完整地执行

    缺点:

    阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
    由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

    ——

    死锁

    在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

    比如总共有资源1资源2线程1先占用了资源1线程2先占用了资源2,但是现在线程1线程2要进行下去都还缺少了资源。
    此时线程1缺少资源2线程2缺少资源1,但是缺少的资源都被对方占用了,双方都在等待对方释放自己需要的那部分资源,但是双方线程没有完成执行完都不会进行释放资源,导致进入了持续的等待状态,即死锁状态,程序无法继续运行下去。

    避免死锁

    优化程序设计,合理分配资源,比如有银行家算法,根据优先级分配资源。
    添加超时时间,等待资源的过程如果超过时间,则自动结束,释放资源。

  • 相关阅读:
    良好基本面引领发展,中国春来将聚势而强?
    ActivityPub 笔记
    单文件组件
    计算机毕设(附源码)JAVA-SSM家庭安防系统
    掌动智能:UI自动化测试工具的重要性和应用
    MySQL-json字段的使用
    【Java面试】这道互联网高频面试题难住了80%的程序员?索引什么时候失效?
    【Shiro】基本使用
    解密Prompt系列31. LLM Agent之从经验中不断学习的智能体
    PTA | Wifi密码
  • 原文地址:https://blog.csdn.net/Goodric/article/details/126228945