zoukankan      html  css  js  c++  java
  • 12.1、多线程:互斥锁、递归锁、信号量、事件


    互斥锁:

    • 为什么要有互斥锁:由于多线程是并行的,如果某一线程取出了某一个数据将要进行操作,但它还没有那么快执行完操作,这时候如果另外一个线程也要操作这个数据,那么这个数据可能会因为两次操作而发生错误
    import time,threading
    
    x=6
    def run1():
        print("run1我拿到了数据:",x)
        print("我现在还不想操作,先睡一下")
        time.sleep(3)
        print("再看一下数据,稳一稳",x)
    
    def run2():
        global x
        print("run2我拿到了数据:", x)
    
        x=5
        print(x)
    
    t1=threading.Thread(target=run1)
    t2=threading.Thread(target=run2)
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()

    image

    • 而多线程的互斥锁机制本质上是:申请一个锁,A线程拿了钥匙【acquire】之后,如果B也想拿到钥匙是不行的,只有等A把钥匙还回来【release】才行
    • 如何使用互斥锁:
      1. 定义一个锁对象:锁对象=threading.Lock()
      2. 请求锁:锁对象.acquire()
      3. 释放锁:锁对象.release()

    使用互斥锁来更改上段代码

    import time,threading
    
    x=6
    def run1():
        lock.acquire()
        global x
        print("run1我拿到了数据,x=",x)
        print("我现在还不想操作,先睡一下")
        time.sleep(3)
        print("再看一下数据,稳一稳,x=",x)
        x+=1
        print("run1操作完毕:x=",x)
        lock.release()
    def run2():
        lock.acquire()
        global x
        print("run2我拿到了数据:", x)
        x+=1
        print("run2操作完毕:x=",x)
        lock.release()
    
    lock=threading.Lock()#生成一个锁对象
    t1=threading.Thread(target=run1)
    t2=threading.Thread(target=run2)
    
    t1.start()
    t2.start()
    start_time=time.time()
    t1.join()
    t2.join()
    print("最终的x=",x)
    print(time.time()-start_time)#3.0多说明,由于受到锁的影响,run2要等待run1释放lock,所以变成了串行

    这种互斥锁在操作系统中可以称作“临界区”,如果想了解更多:

    https://baike.baidu.com/item/%E4%B8%B4%E7%95%8C%E5%8C%BA/8942134?fr=aladdin

    image


    递归锁:

    • 为什么要有递归锁:互斥锁本质上是阻止其他线程进入,如果有两个需要阻止其他线程进入的操作【像两个人过独木桥】,那么需要两个锁,而想要锁上第二个如果直接用第一个锁的acquire会失败,因为第一个锁还没release,我们可以选择再定义一个互斥锁对象来acquire,但这仅仅是两层的情况下,如果多层的吧,那么就需要定义好几个互斥锁对象了【而且由于对象变多,有时候会因为互相调用锁而发生死锁】。递归锁就是为了处理这种情况,递归锁对象允许多次acquire和多次release
      • 发生死锁的情况[A拿到A锁,想要拿B锁,B拿着B锁,想要A锁]

    【以过独木桥为例】:桥只能容一个人通过,A只能看得到北边桥上有没有人,看不到南边桥有没有人,当他看到北边桥没人就会过桥,等到他到桥中间才能看到南边桥有没有人,B情况相反:【于是当两个人一起过桥的时候就会发生死锁】

    import threading,time
    
    
    
    
    """
    A只能看得到北边桥上有没有人,看不到南边桥有没有人,
    当他看到北边桥没人就会过桥,等到他到桥中间才能看到南边桥有没有人
    """
    def A():
        lockNorth.acquire()#拿到北边桥的锁
        print("A过桥北")
        time.sleep(3)#过桥中
        lockSorth.acquire()#企图过到南边桥,
        print("A过桥南")
        time.sleep(3)  # 过桥中
        lockSorth.release()
        lockNorth.release()
        print("A过桥成功")
    
    """
    B只能看得到南边桥上有没有人,看不到北边桥有没有人,
    当他看到南边桥没人就会过桥,等到他到桥中间才能看到北边桥有没有人
    """
    def B():
        lockSorth.acquire()  # 企图过到南边桥,
        print("B过桥南")
        time.sleep(3)  # 过桥中
        lockNorth.acquire()  # 拿到北边桥的锁
        print("B过桥北")
        time.sleep(3)  # 过桥中
        lockNorth.release()
        lockSorth.release()
        print("B过桥成功")
    
    
    lockNorth=threading.Lock()
    lockSorth=threading.Lock()
    
    tA=threading.Thread(target=A)
    tB=threading.Thread(target=B)
    tA.start()
    tB.start()
    
    tA.join()
    tB.join()

    image

    • 递归锁的本质是:本质上还是一个锁,但如果在一个线程里面可以多次acquire。【因为只有一个锁,所以不会发生互相调用的死锁,而因为可以多次调用,所以可以锁多次】
    • 如何使用递归锁:
      1. 定义一个锁对象:递归锁对象=threading.RLock()
      2. 请求锁:锁对象.acquire()
      3. 释放锁:锁对象.release()

    使用递归锁来解决上面的死锁问题:

    import threading,time
    
    """
    A只能看得到北边桥上有没有人,看不到南边桥有没有人,
    当他看到北边桥没人就会过桥,等到他到桥中间才能看到南边桥有没有人
    """
    def A():
        lock.acquire()#拿到北边桥的锁
        print("A过桥北")
        time.sleep(3)#过桥中
        lock.acquire()#企图过到南边桥,
        print("A过桥南")
        time.sleep(3)  # 过桥中
        lock.release()
        lock.release()
        print("A过桥成功")
    
    """
    B只能看得到南边桥上有没有人,看不到北边桥有没有人,
    当他看到南边桥没人就会过桥,等到他到桥中间才能看到北边桥有没有人
    """
    def B():
        lock.acquire()  # 拿南桥锁,
        print("B过桥南")
        time.sleep(3)  # 过桥中
        lock.acquire()  # 企图拿北桥的锁
        print("B过桥北")
        time.sleep(3)  # 过桥中
        lock.release()
        lock.release()
        print("B过桥成功")
    
    
    lock=threading.RLock()
    
    tA=threading.Thread(target=A)
    tB=threading.Thread(target=B)
    tA.start()
    tB.start()
    
    tA.join()
    tB.join()

    image

    【由于本质是一把锁,A拿到锁后,B要等待】


    信号量:

    • 什么是信号量:

    image

    信号量可以限制进入的线程的数量。

    • 如何使用信号量:
      1. 创建信号量对象:信号量对象=threading.BoundedSemaphore(x),x是限制进程的数量
      2. 当有进程需要进入的时候,调用acquire()来减少信号量:信号量对象.acquire()
      3. 当有进程离开的时候,调用release()来增加信号量:信号量对象.release()
    import threading,time
    
    
    def run():
        s.acquire()
        print("hello")
        time.sleep(1.5)
        s.release()
    
    s=threading.BoundedSemaphore(3)#限制3个
    
    threading_list=[]
    for i in range(12):#创建12个线程
        obj=threading.Thread(target=run)
        obj.setDaemon(True)  # 设置守护线程,避免干扰主线程运行,并行等待
        obj.start()
    
    for i in range(4):
        print("")#为了把结果分割,可以清楚看出分为了三组
        time.sleep(1.5)
    #结果分为三组是因为运行的太快了,三个线程装入的时间差太小

    image


    事件:

    • 什么是事件:当发生线程发生一件事的时候如果要提醒另外一个线程,使用事件。双方共享该事件对象【等待的一方会阻塞而进行等待】,当一方更改事件对象的时候,另外一方也能知道【以读者-写者为例:读者要等写者告诉他去读才会去读,写者写完后要设置一个事件,当该事件设置时,读者就会来读】
    • 如何使用事件:
      1. 创建事件对象:事件对象=threading.Event()
      2. 设置事件:事件对象.set()    判断事件是否set:事件对象.is_set(),等待事件set:事件对象.wait()
      3. 清除事件:事件对象.clear() 
    import threading,time
    
    
    def read():
        while True:
            if event.is_set():
                print("事件已设置,我要读了!!!!")
                time.sleep(1)
            else:#事件未设置
                print("还没写好,我要等咯")
                event.wait()#那么就等着咯
                #如果等到了
                print("终于等到了!那么我又可以读了")
                time.sleep(1)
    
    def write():
        event.clear()#初始设空
        while True:
            time.sleep(3)#
            event.set()#设置事件,一旦set,那么读者wait就有返回了,读者可以继续运行了
            print("write:写好了")
            time.sleep(2)#等人读
            event.clear()#清除事件
    
    
    event=threading.Event() #创建事件对象
    
    t1=threading.Thread(target=write)
    t2=threading.Thread(target=read)
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    
    """结果显示:读者确实一直在等待写者写好"""

    image


  • 相关阅读:
    Django 模版当中使用中文 UnicodeDecodeError at / 问题
    sql中索引不会被用到的几种情况
    Django安装
    分享一个webapi接口性能测试的工具
    orleans 2.0 进阶之自定义持久化储存
    centos7 .net core 使用supervisor守护进程,可以后台运行
    将.net core 发布到Linux上的一些坑
    .net core 部署在Linux系统上运行的环境搭建总结
    .net core 基于NPOI 的excel导入导出类,支持自定义导出哪些字段,和判断导入是否有失败的记录
    基于Ace Admin 的菜单栏实现
  • 原文地址:https://www.cnblogs.com/progor/p/8433975.html
Copyright © 2011-2022 走看看