zoukankan      html  css  js  c++  java
  • 线程相关锁

    线程相关锁

    一、线程同步锁(互斥锁)

    多线程抢占资源的数据安全问题

    # 案例一
    from threading import Thread
    import time
    
    n = 10
    def func():
        global n
        temp = n
        time.sleep(0.2)
        n = temp - 1
    
    if __name__ == '__main__':
        lis = []
        for i in range(10):
            t = Thread(target=func)
            t.start()
    
            lis.append(t)
        for i in lis:
            i.join()
        print(n)  # 9
    
    '''
    因为将n的递减操作拆分为两个阶段,一个赋值,一个减值,中间休眠0.2s
    此时,当10个线程去修改n的时候,就会出现在0.2s之前10个线程拿到的n都是10
    最后每个线程返回的修改值都是9,这样就产生了数据不同步的安全问题
    所以,当延迟时间过长或者数据量过大时,使用多线程同时访问同一个数据时,不安全
    '''
    
    # 案例二
    from threading import Thread
    
    n = 0
    def task():
        global n
        for i in range(100000):
            n += 1
    
    if __name__ == '__main__':
        lis = []
        for i in range(3):
            t = Thread(target=task)
            t.start()
            lis.append(t)
        for i in lis:
            i.join()
        print(n)  # 274155
    
    '''
    当启动三个线程来执行task时
    发现结果永远小于300000
    这也验证了多个线程修改同一个数据时会引发数据安全问题
    **********所以,有了进程锁的概念***********
    '''
    

    使用线程同步锁解决数据安全问题

    # 案例一解决方法
    from threading import Thread,Lock
    import time
    
    n = 10
    lock = Lock()
    def func():
        global n
        lock.acquire()
        temp = n
        time.sleep(0.2)
        n = temp - 1
        lock.release()
    
    if __name__ == '__main__':
        lis = []
        for i in range(10):
            t = Thread(target=func)
            t.start()
    
            lis.append(t)
        for i in lis:
            i.join()
        print(n)  # 0
    
    # 案例二解决方
    from threading import Thread,Lock
    n = 0
    lock = Lock()
    def task():
        global n
        lock.acquire()
        for i in range(100000):
            n += 1
        lock.release()
    if __name__ == '__main__':
        lis = []
        for i in range(3):
            t = Thread(target=task)
            t.start()
            lis.append(t)
        for i in lis:
            i.join()
        print(n)  # 300000
        
    '''
    给要处理的数据加一把锁,限定同一时间只能有一个线程来访问这段代码
    同样,在保证了数据安全的同时,牺牲了效率
    '''
    

    二、死锁

    ​ 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁:

    # 著名的科学家吃面问题
    from threading import Thread,Lock
    import time
    
    noodle_lock = Lock()
    fork_lock = Lock()
    
    def eat1(name):
        noodle_lock.acquire()
        print(f'{name}拿到面条了')
        fork_lock.acquire()
        time.sleep(1)
        print(f'{name}拿到叉子了')
        print(f'{name}吃面')
        fork_lock.release()
        noodle_lock.release()
    
    def eat2(name):
        fork_lock.acquire()
        print(f'{name}拿到叉子了')
        time.sleep(1)
        noodle_lock.acquire()
    
        print(f'{name}拿到面条了')
        print(f'{name}吃面')
        noodle_lock.release()
        fork_lock.release()
    
    Thread(target=eat1,args=('alex',)).start()
    Thread(target=eat2,args=('nick',)).start()
    Thread(target=eat1,args=('tank',)).start()
    Thread(target=eat2,args=('egon',)).start()
    '''
    alex拿到面条了
    alex拿到叉子了
    alex吃面
    nick拿到叉子了
    tank拿到面条了
    '''
    

    三、递归锁

    ​ 死锁的解决方法就是递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

    ​ 这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

    from threading import Thread,RLock
    import time
    
    fork_lock = noodle_lock = RLock()
    
    
    def eat1(name):
        noodle_lock.acquire()
        print(f'{name}拿到面条了')
        fork_lock.acquire()
        print(f'{name}拿到叉子了')
        print(f'{name}吃面')
        fork_lock.release()
        noodle_lock.release()
    
    def eat2(name):
        fork_lock.acquire()
        print(f'{name}拿到叉子了')
        time.sleep(1)
        noodle_lock.acquire()
    
        print(f'{name}拿到面条了')
        print(f'{name}吃面')
        noodle_lock.release()
        fork_lock.release()
    
    Thread(target=eat1,args=('alex',)).start()
    Thread(target=eat2,args=('nick',)).start()
    Thread(target=eat1,args=('tank',)).start()
    Thread(target=eat2,args=('egon',)).start()
    
    '''
    alex拿到面条了
    alex拿到叉子了
    alex吃面
    nick拿到叉子了
    nick拿到面条了
    nick吃面
    tank拿到面条了
    tank拿到叉子了
    tank吃面
    egon拿到叉子了
    egon拿到面条了
    egon吃面
    '''
    

    四、全局解释器锁GIL

    ​ Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
      对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

      在多线程环境中,Python 虚拟机按以下方式执行:

      a、设置 GIL;

      b、切换到一个线程去运行;

      c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

      d、把线程设置为睡眠状态;

      e、解锁 GIL;

      d、再次重复以上所有步骤。
      在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

    GIL 就是当线程访问cpu对数据进行修改时介于两者之间的一把锁,其实就是Cpython的解释器起作用。
    	他控制了同一时刻只能有一个线程来访问数据,将此线程设置为睡眠状态
    	当线程对数据修改完毕了,GIL就会释放该线程
        之后才允许下一个线程来访问数据
    
    GIL锁的是线程
    GIL导致的不能并发问题是Cpython的特性,目前还不能得以解决!
    

  • 相关阅读:
    JAVA 问题
    WebStrom配置多个项目的Dweployment时,设置默认的启动配置
    C#中有关数组和string引用类型或值类型的判断
    Delegate(代理)异常:该委托必须有一个目标
    RMAN BACKUP
    Oracle ORA-01033: 错误解决办法
    微信公众号开发 接口配置信息 配置失败
    使用JAVA开发微信公众平台(一)——环境搭建与开发接入
    微信开发准备(四)--nat123内网地址公网映射实现
    nat123安装启动教程帮助
  • 原文地址:https://www.cnblogs.com/dadazunzhe/p/11545522.html
Copyright © 2011-2022 走看看