• 第十五章、Python多线程同步锁,死锁和递归锁


    第十五章、Python多线程同步锁,死锁和递归锁

    1. 引子:

    1.创建线程对象
    t1 = threading.Thread(target=say,args=('tony',))
    2.启动线程
    t1.start()
    后面又说了两个点就是join和守护线程的概念
    

    以上就是python多线程的基本使用

    ​ 说明:前面说的两个功能是相互独立的,相互不干涉的,不会用到同享的资源或者数据,如果我们多个线程要用到相同的数据,那么就会存在资源争用和锁的问题,不管在什么语言中,这个都是不能避免的。 那么接下来讲讲同步锁,死锁和递归锁的使用

    2.同步锁

    ​ 锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁。

    ​ 适用同步锁的例子如下:

    import threading
    import time
    
    num = 100
    
    def fun_sub():
        global num
        # num -= 1
        num2 = num
        time.sleep(0.001)
        num = num2-1
    
    if __name__ == '__main__':
        print('开始测试同步锁 at %s' % time.ctime())
    
        thread_list = []
        for thread in range(100):
            t = threading.Thread(target=fun_sub)
            t.start()
            thread_list.append(t)
    
        for t in thread_list:
            t.join()
        print('num is %d' % num)
        print('结束测试同步锁 at %s' % time.ctime())
    -----------------------------------------------------
    开始测试同步锁 at Sun Apr 28 09:56:45 2019
    num is 91
    结束测试同步锁 at Sun Apr 28 09:56:45 2019
    

    这样的例子描述:创建100的线程,然后每个线程去从公共资源num变量去执行减1操作,按照正常情况下面,等到代码执行结束,打印num变量,应该得到的是0,因为100个线程都去执行了一次减1的操作。

    这样的问题是:发现结果不是0而是91

    让我们整理一下代码思路

    1.因为GIL,只有一个线程(假设线程1)拿到了num这个资源,然后把变量赋值给num2,sleep 0.001秒,这时候num=100
    2.当第一个线程sleep 0.001秒这个期间,这个线程会做yield操作,就是把cpu切换给别的线程执行(假设线程2拿到个GIL,获得cpu使用权),线程2也和线程1一样也拿到num,返回赋值给num2,然后sleep,这时候,其实num还是=100.
    3.线程2 sleep时候,又要yield操作,假设线程3拿到num,执行上面的操作,其实num有可能还是100
    4.等到后面cpu重新切换给线程1,线程2,线程3上执行的时候,他们执行减1操作后,其实等到的num其实都是99,而不是顺序递减的。
    5.其他剩余的线程操作如上

    解决方案:这里就要借助于python的同步锁了,也就是同一时间只能放一个线程来操作num变量,减1之后,后面的线程操作来操作num变量。看看下面我们怎么实现。

    import threading
    import time
    
    num = 100
    
    def fun_sub():
        global num
        lock.acquire()
        print('----加锁----')
        print('现在操作共享资源的线程名字是:',t.name)
        num2 = num
        time.sleep(0.001)
        num = num2-1
        lock.release()
        print('----释放锁----')
    
    if __name__ == '__main__':
        print('开始测试同步锁 at %s' % time.ctime())
    
        lock = threading.Lock() #创建一把同步锁
    
        thread_list = []
        for thread in range(100):
            t = threading.Thread(target=fun_sub)
            t.start()
            thread_list.append(t)
    
        for t in thread_list:
            t.join()
        print('num is %d' % num)
        print('结束测试同步锁 at %s' % time.ctime())
     ------------------------------------------------
     .......
    ----加锁----
    现在操作共享资源的线程名字是: Thread-98
    ----释放锁----
    ----加锁----
    现在操作共享资源的线程名字是: Thread-100
    ----释放锁----
    num is 0
    结束测试同步锁 at Sun Apr 28 12:08:27 2019
    

    思路:看到上面我们给中间的减1代码块,加个一把同步锁,这样,我们就可以得到我们想要的结果了,这就是同步锁的作用,一次只有一个线程操作同享资源。

    3.死锁

    引子:

    ​ 死锁的这个概念在很多地方都存在,比较在数据中,大概介绍下死锁是怎么产生的

    # 线程1拿到了(锁头2)想要往下执行需要(锁头1),
    # 线程2拿到了(锁头1)想要往下执行需要(锁头2)
    # 互相都拿到了彼此想要往下执行的必需条件,互相都不放手里的锁头.
    # 产生了死锁问题
    

    产生原因:python中在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。

    from threading import Thread,Lock
    mutex1 = Lock()
    mutex2 = Lock()
    import time
    class MyThreada(Thread):
        def run(self):
            self.task1()
            self.task2()
        def task1(self):
            mutex1.acquire()
            print(f'{self.name} 抢到了 锁1 ')
            mutex2.acquire()
            print(f'{self.name} 抢到了 锁2 ')
            mutex2.release()
            print(f'{self.name} 释放了 锁2 ')
            mutex1.release()
            print(f'{self.name} 释放了 锁1 ')
    
        def task2(self):
            mutex2.acquire()
            print(f'{self.name} 抢到了 锁2 ')
            time.sleep(1)
            mutex1.acquire()
            print(f'{self.name} 抢到了 锁1 ')
            mutex1.release()
            print(f'{self.name} 释放了 锁1 ')
            mutex2.release()
            print(f'{self.name} 释放了 锁2 ')
    
    for i in range(3):
        t = MyThreada()
        t.start()
    

    那么,为了解决这个死锁问题,就引入了递归锁方案

    4.递归锁RLock

    原理:

    ​ 为了支持在同一线程中多次请求同一资源,python提供了"递归锁":threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源

    不多说,放代码

    from threading import Thread,Lock,RLock
    # mutex1 = Lock()
    # mutex2 = Lock()
    mutex1 = RLock()
    mutex2 = mutex1
    
    import time
    class MyThreada(Thread):
        def run(self):
            self.task1()
            self.task2()
        def task1(self):
            mutex1.acquire()
            print(f'{self.name} 抢到了 锁1 ')
            mutex2.acquire()
            print(f'{self.name} 抢到了 锁2 ')
            mutex2.release()
            print(f'{self.name} 释放了 锁2 ')
            mutex1.release()
            print(f'{self.name} 释放了 锁1 ')
    
        def task2(self):
            mutex2.acquire()
            print(f'{self.name} 抢到了 锁2 ')
            time.sleep(1)
            mutex1.acquire()
            print(f'{self.name} 抢到了 锁1 ')
            mutex1.release()
            print(f'{self.name} 释放了 锁1 ')
            mutex2.release()
            print(f'{self.name} 释放了 锁2 ')
    
    
    for i in range(3):
        t = MyThreada()
        t.start()
    

    总结:

    ​ 上面我们用一把递归锁,就解决了多个同步锁导致的死锁问题。大家可以把RLock理解为大锁中还有小锁,只有等到内部所有的小锁,都没有了,其他的线程才能进入这个公共资源。

    5. 大总结

    ​ 还有一点,并不是所有的多线程都存在数据不同步、死锁的问题,但在访问共享资源的时候,锁是一定要存在了, 所以我们在代码里面加锁的时候,要注意在什么地方加,对性能的影响最小,这个就靠对逻辑的理解了。

  • 相关阅读:
    用脚本保存prefab
    如何在Unity 3D中掷骰子
    转发收藏【原创】浅谈UGUI的ETC1+A的纹理压缩方案总结
    Unity鼠标拖拽控制人物的左右旋转
    蛋哥的学习笔记之-基于Unity的Shader编程:X-1 音乐水波特效
    xlua中hotfix简单实用
    tolua调用C#中的静态类
    scut和unity之间收发请求返回
    scut服务器unity配置
    HTTP网络请求
  • 原文地址:https://www.cnblogs.com/demiao/p/11543888.html
Copyright © 2020-2023  润新知