• 多线程的使用


    一、并发和并行

    1. 多任务的概念:就是操作系统可以同时运行多个任务

    2. 单核CPU可不可以执行多任务:

      1. 也可以执行多任务:由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
        答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,
        再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,
        由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样
      2. 真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,
        所以,操作系统也会自动把很多任务轮流调度到每个核心上执行
    3. 什么是并发和并行

      1. 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已),并发是包含并行的
      1. 并行:指的是在同一时刻去执行,任务数小于等于cpu核数,即任务真的是一起执行
        使用python实现同时执行多个任务(并发:进程、线程、协程都可以实现)

    在这里插入图片描述

    二、线程的使用

    并发能做什么呢,一个简单的例子,假如一个人要同时完成两件事情,需要多久?

    用代码实现如下:

    import time
    
    def func1():
        for i in range(5):
            print("------正在做事情1------")
            time.sleep(1)
    
    
    def func2():
        for i in range(6):
            print("------正在做事情2------")
            time.sleep(1)
            
    st = time.time()
    func1()
    func2()
    et = time.time()
    print("一共需要完成的时间",et - st)    
    
    # ======》 一共需要11秒
    # 先做事情1 
    # 在做事情2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    假如2个人要同时完成两件事情,需要多久,这就需要用到线程模块,多线程一起执行。

    一 、线程模块的详细使用

    1. threading模块介绍:

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

      快速使用:from threading import Thread ,使用的是Thread类

    2. 创建线程对象: threading.Thread(target=任务函数)

      其中参数target指定线程执行的任务 (函数)

    3. 线程参数的使用

      Thread类有以下参数:

      1、target参数: 指定任务函数

      import time
      from threading import Thread
      
      
      def func1():
          for i in range(5):
              print("-正在做事情1------")
              time.sleep(1)
              
      # 创建一个线程对象
      aa = Thread(target=func1)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      2、name参数: 设置线程名

      	# 创建一个线程对象
      	aa = Thread(target=func1,name="henry")
      
      • 1
      • 2

      3.、args参数: 给任务函数传参(参数传递的是元祖类型)

      	def func1(aa):
          for i in range(5):
              print("---{}---正在做事情1------".format(aa)
              time.sleep(1)
              
      	# 创建一个线程对象
      	aa = Thread(target=func1,args=('张三',))
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      4、kwargs参数:给任务函数传参(参数传递的是字典类型)

      	def func1(aa):
          for i in range(5):
              print("---{}---正在做事情1------".format(aa)
              time.sleep(1)
              
      	# 创建一个线程对象
      	aa = Thread(target=func1,kwargs={'aa': "李四"})
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      5、daemon参数:设置是否作为守护线程(bool值)

      1. 设置子线程守护主线程执行(主线程结束,守护线程自动结束)
      2. 如果没有设置守护线程,主线程跑完,子线程没有跑完,还会继续跑下去,不会有影响
      3. 如果设置了守护线程,主线程跑完,子线程没有跑完,当前线程全部结束(包括子线程,不管是否跑完)
      4. 如果多个子线程,一个设置了守护线程,一个没有设置守护线程,主线程结束,子线程还会继续跑下去,不会有影响
      def func1(aa):
         for i in range(5):
             print("---{}---正在做事情1------".format(aa))
             # print("---正在做事情1------")
             time.sleep(1)
      
      
      def func2(aa):
         for i in range(6):
             print("----{}--正在做事情2------".format(aa))
             time.sleep(1)
         
      # 创建一个线程对象       
      t1 = Thread(target=func1, args=('张三',),daemon=True)      # 设置守护线程 
      t2 = Thread(target=func2, kwargs={'aa': "李四"},daemon=True)       # 设置守护线程
      
      # 启动线程执行
      t1.start()
      t2.start()
      
      time.sleep(2)
      print("----主线程执行结束-----------")
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    4. 线程方法的使用

      Thread类提供了以下方法:

      1、start()方法: 启动线程执行

      # 启动线程执行
      t1.start()
      t2.start()
      
      • 1
      • 2
      • 3

      2、join([time]): 设置主线程等待的时间

      1. 设置主线程会等待time秒后再往下执行,不设置time,则默认为子线程结束,多个子线程之设置的值会叠加
      2. 多个子线程同时使用join设置的时间会叠加
      def func2():
          for i in range(6):
              print("------正在做事情2------")
              time.sleep(1)
      
      # 创建一个线程对象
      t1 = Thread(target=func2)     # 子线程t1
      
      # # 子线程t1启动执行
      t1.start()
      
      # 主线程等待时间
      t1.join()    # 让主线程等待t1执行完,再往下执行
      
      # 主线程等待子线程执行完
      for i in range(10):
      	print("主线程全部执行完成——————————————————————————")
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      3、run方法 :设置线程执行的逻辑代码

      # 如何实现线程类的实现实现多线程
      1. 创建一个类需要继承 Thread类
      2. 设置执行函数(run方法)
      3. 设置线程参数
      4. 启动线程
      
      # 继承类来创建线程
      class GetDataThread(Thread):      # 继承Thread类
      
          def run(self):
              """线程执行的逻辑代码"""
              for i in range(5):
                  time.sleep(1)
                  print("---{}--data-----{}".format(self.name, i))
      # 多线程
      t1 = GetDataThread(name='线程1')
      t2 = GetDataThread(name='线程2')
      
      # 启动线程
      t1.start()
      t2.start()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21

      4、isDaemon: 判断是否为守护线程

      5、isAlive: 判断线程支持存活(处于执行状态)

      6、is_alive:判断线程支持存活(处于执行状态)

      7、getName:获取线程名

      print("线程名为:",t1.getName())
      print("是否为守护线程:",t1.isDaemon())
      print("线程是否处于执行状态:",t1.isAlive())
      print("线程是否处于执行状态:",t1.is_alive())
      
      • 1
      • 2
      • 3
      • 4

      在这里插入图片描述

    5. 多线程共享全局变量的问题

      1、什么是多线程-共享全局变量

      1. 线程之间是共用同一块内存的,那么线程可以共享全局变量
      2. 在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据

      2、多线资源竞争的问题

      1. 如果多个线程操作同一个全局资源,在频繁的切换线程会出现资源竞争问题,而导致数据丢失或者被覆盖

      3、在多线程的时候,什么情况下会触发切换线程:

      1. 执行时间达到指定的阈值:0.005,(python中默认为0.005)
      2. 线程执行遇到IO(input,output)输入输出操作:如网络IO,文件IO,等待(time.sleep)
      3. 获取python线程切换的阈值: print(sys.getswitchinterval())

      例如:在下面的函数中,a为全局变量,两个函数中都有,两个线程都去执行,导致a的值不正确

      from threading import Thread
      
      a = 0    # 全局变量
      
      def work1():
          global a
          for i in range(500000):
              a += 1
          print("work1执行完,a的值:", a)
      
      
      def work2():
          global a
          for i in range(500000):
              a += 1
          print("work2执行完,a的值:", a)
      
      t1 = Thread(target=work1)
      t2 = Thread(target=work2)
      t1.start()
      t2.start()
      
      # 执行结果
      work执行完,a的值: 500000
      work2执行完,a的值: 824157
      a: 824157
      
      • 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
    6. 多线程共享全局资源竞争如何解决

      1、多线程操作时的解决思路:

      1. 线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引⼊互斥锁
      2. 互斥锁为资源引⼊⼀个状态:锁定/⾮锁定
      3. 某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改直到该线程释放资源,将资源的状态变成“⾮锁定”,其他的线程才能再次锁定该资源
      4. 互斥锁为资源引⼊⼀个状态:锁定/⾮锁定
      5. 斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性
      6. threading模块中定义了Lock类,可以方便的处理锁定

      2、如何解决:

      1. 关键性的代码进行加锁,控制线程的执行,避免同时获取数据,对于全局数据修改的那行关键性的代码使用锁锁起来,防止执行到这行代码的时候进行线程切换
      2. 使用队列来存储数据(队列在设置和获取数据时都实现了锁的功效)

      3、Threading模块中定义了Lock类,可以方便的处理锁定:

      1. 创建锁之前需要实例化: 变量 = threading.Lock()
      2. 锁定方法 - acquire(): 变量.acquire()
      3. 释放锁,待锁定 - release(): 变量.release()

      4、注意点:

      1. 如果这个锁之前是没有上锁的,那么acquire不会堵塞
      2. 如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止

      5、锁的好处:

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

      6、锁的坏处:

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

      例如:还是这个函数,a为全局变量,两个函数中都有,两个线程都去执行,去给两个函数分别加上锁,在执行看:

      from threading import Thread,Lock
      
      a = 0    # 全局变量
      
      # 实例化一个锁对象
      lockA = Lock()
      
      def work1():
          global a
          for i in range(500000):
              lockA.acquire()    # 锁定
          	a += 1
          	lockA.release()   # 释放锁定
          print("work1执行完,a的值:", a)
      
      
      def work2():
          global a
          for i in range(500000):
              lockA.acquire()    # 锁定
          	a += 1
          	lockA.release()   # 释放锁定
          print("work2执行完,a的值:", a)
      
      t1 = Thread(target=work1)
      t2 = Thread(target=work2)
      t1.start()
      t2.start()
      t1.join()
      t2.join()
      
      # 执行结果
      work执行完,a的值: 867609
      work2执行完,a的值: 1000000
      a: 1000000
      
      • 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
      • 35
  • 相关阅读:
    产品经理技术脑:RESTful API
    基于TensorFlow 2.3.0 的手势识别系统设计
    SpringCloud Alibaba - 分布式事务理论(CAP 定理 和 BASE 理论)
    RabbitMQ-发布/订阅模式
    小程序开发直传腾讯云操作步骤
    【U8+】用友U8-UFO录入关键字,计算后乱码
    go-micro开发RPC服务的方法及其运行原理
    2023-09-22力扣每日一题
    各报文段格式集合
    vue项目打包成H5apk中使用语音播放
  • 原文地址:https://blog.csdn.net/qq_40236497/article/details/125502631