多线程的使用

一、并发和并行

  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实现同时执行多个任务(并发:进程、线程、协程都可以实现)

多线程的使用_第1张图片

二、线程的使用

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

用代码实现如下:

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

假如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)
    

    2、name参数: 设置线程名

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

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

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

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

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

    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("----主线程执行结束-----------")
    
  4. 线程方法的使用

    Thread类提供了以下方法:

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

    # 启动线程执行
    t1.start()
    t2.start()
    

    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("主线程全部执行完成——————————————————————————")
    

    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()
    

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

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

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

    7、getName:获取线程名

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

    在这里插入图片描述

  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
    
  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
    

你可能感兴趣的:(Python高阶知识,python,开发语言)