zoukankan      html  css  js  c++  java
  • python线程互斥锁递归锁死锁

    一、为什么有了GIL还要给线程加锁

    先说一下GIL,所谓的GIL,也叫全局解释器锁,它限制了任何时候都只能有一个线程进入CPU进行计算,所以python所谓的多线程并不能真正的并行。

    那为什么有了GIL还需要给线程加锁呢?不是直接一个线程处理完一个数据才轮到下一个线程进行吗?线程锁不是多此一举?

    解决这个问题,我们得更深入到底层看看代码是怎么在CPU上运行的。在这里引入一个概念:原子操作

    什么是原子操作

    所谓的原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,不会运行到一半,然后CPU切换到另外的线程。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱。

    像 C语言的i++和python中的+=,-=,*=,/=都不是原子操作,他们在被翻译成机器指令时实际上是分三个步骤的,比如 i-=1 这个操作本质是这样的:

    1、先把内存中的1存储在CPU的寄存器中

    2、CPU进行计算,减一

    3、将寄存器的内容写到内存中。

    在1-3这个过程中,线程完全有可能被切换,所以可能导致线程数据的不安全。所以加锁是必要的。我们看看下面的一个例子。

    from threading import Lock,Thread
    n = 10000000
    def func():
        global n
        for i in range(1000000):
            n -= 1
    t_lst = []
    for i in range(10):
        t = Thread(target=func)
        t.start()
        t_lst.append(t)
    for i in t_lst:i.join()
    print(n)

    上面代码过程就是用十个线程去将一个数减到0,但是运行结果如下:

     

     所以这就验证了线程数据的不安全性。下面是加锁的版本

    from threading import Lock,Thread
    n = 10000000
    def func(lock):
        global n
        for i in range(1000000):
            lock.acquire()
            n = n - 1
            lock.release()
    t_lst = []
    lock = Lock()
    for i in range(10):
        t = Thread(target=func,args=(lock,))
        t.start()
        t_lst.append(t)
    for i in t_lst:i.join()
    print(n)

     

    二、互斥锁

    同一时间只能有一个任务持有互斥锁,而且只有这个任务可以对互斥锁进行解锁。当无法获取锁时,线程进入睡眠等待状态。 

    其实上面的例子用到的就是互斥锁。当一个线程在操作数据n时候,其他线程是不允许对n进行操作的。

    三、死锁

    所谓的死锁就是指由多个线程直接,各自持有某些资源,又在申请其他线程所持有的资源,各自坚持着都不释放资源,一直坚持着,这就是死锁。

    先不下明确的定义,后面再仔细讨论。我们先来看看一个死锁的例子。

    科学家吃面问题:几个科学家一起吃面,必须先申请面和申请到叉子才能开吃。

    import time
    from threading import Thread,Lock
    def eat1(noodle_lock,fork_lock,name):
        noodle_lock.acquire()
        print(name,'拿到了面')
        fork_lock.acquire()
        print(name,'拿到了叉子')
        time.sleep(1)
        print(name,'吃到了面')
        fork_lock.release()
        noodle_lock.release()
        print(name, '放下了面')
        print(name, '放下了叉子')
    def eat2(noodle_lock,fork_lock,name):
        fork_lock.acquire()
        print(name, '拿到了叉子')
        noodle_lock.acquire()
        print(name, '拿到了面')
        print(name, '吃到了面')
        noodle_lock.release()
        print(name, '放下了面')
        fork_lock.release()
        print(name, '放下了叉子')
    name_list1 = ['特斯拉','牛顿']
    name_list2 = ['法拉第','爱迪生']
    noodle_lock  = Lock()
    fork_lock = Lock()
    for i in name_list1:
        t = Thread(target=eat1,args=(noodle_lock,fork_lock,i))
        t.start()
    for i in name_list2:
        t = Thread(target=eat2, args=(noodle_lock, fork_lock, i))
        t.start()

    一个拿着叉子在等面,一个拿着面在等叉子。一直僵持着,这就是死锁。

    四、递归锁

     所谓的递归锁就是指一个线程可以多次申请同一把锁,但是不会造成死锁。这就可以用来解决上面的死锁问题

    import time
    from threading import Thread,RLock
    def eat1(noodle_lock,fork_lock,name):
    noodle_lock.acquire()
    print(name,'拿到了面')
    fork_lock.acquire()
    print(name,'拿到了叉子')
    time.sleep(1)
    print(name,'吃到了面')
    fork_lock.release()
    noodle_lock.release()
    print(name, '放下了面')
    print(name, '放下了叉子')
    def eat2(noodle_lock,fork_lock,name):
    fork_lock.acquire()
    print(name, '拿到了叉子')
    noodle_lock.acquire()
    print(name, '拿到了面')
    print(name, '吃到了面')
    noodle_lock.release()
    print(name, '放下了面')
    fork_lock.release()
    print(name, '放下了叉子')
    name_list1 = ['特斯拉','牛顿']
    name_list2 = ['法拉第','爱迪生']
    noodle_lock=fork_lock = RLock()
    for i in name_list1:
    t = Thread(target=eat1,args=(noodle_lock,fork_lock,i))
    t.start()
    for i in name_list2:
    t = Thread(target=eat2, args=(noodle_lock, fork_lock, i))
    t.start()

     

     下面在仔细讨论一下死锁。

    五、死锁产生的四个必要条件

    1、互斥条件:当一个进程在访问一个资源的时候,其他进程只能等待。即任何时候一个资源只能给一个进程使用。

    2、不可剥夺条件:一个进程在访问一个资源时,其他进程只能等该进程使用完释放资源,不可强行剥夺。

    3、请求和保持条件:当一个进程在申请它所需的资源时,并不会释放已有的资源。

    4、在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。 

    只要发生死锁,那么上面四个条件一定都成立。所以只要破坏其中一个,就可以打破死锁。

  • 相关阅读:
    关于size_t
    图的搜索算法之迷宫问题和棋盘马走日问题
    螺旋矩阵与螺旋队列
    内存分配问题
    质数的判断
    全局变量、静态全局变量、静态局部变量和局部变量的区别
    程序员必知之代码规范标准
    字符串查找与类型转换(C/C++)
    sizeof与strlen()的用法与区别
    关于C++的输入输出流(cin、sstream和cout)
  • 原文地址:https://www.cnblogs.com/linshuhui/p/9704128.html
Copyright © 2011-2022 走看看