• 10 并发编程-(线程)-GIL全局解释器锁&死锁与递归锁


    一、GIL全局解释器锁

    1、引子

    在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势

    首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。

    就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。>有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。

    所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

    2、GIL介绍

    GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。

    可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。

    要想了解GIL,首先确定一点:每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生3个不同的python进程

    如果多个线程的target=work,那么执行流程是

    多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行

    3、GIL与Lock

    机智的同学可能会问到这个问题:Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock?

    首先,我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

    然后,我们可以得出结论:保护不同的数据就应该加不同的锁。

    最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock,如下图

    1、100个线程去抢GIL锁,即抢执行权限
    2、肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
    3、极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
    4、直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程

    4、GIL与多线程(计算密集型用 多进程 I/O密集型用 多线程)

    应用:

    多线程用于IO密集型,如socket,爬虫,web

    多进程用于计算密集型,如金融分析

    如果并发的多个任务是计算密集型:多进程效率高

    from multiprocessing import Process
    from threading import Thread
    import os,time
    def work():
        res=0
        for i in range(100000000):
            res*=i
    
    
    if __name__ == '__main__':
        l=[]
        print(os.cpu_count()) #本机为4核
        start=time.time()
        for i in range(4):
            p=Process(target=work) #耗时5s多
            p=Thread(target=work) #耗时18s多
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop=time.time()
        print('run time is %s' %(stop-start))
    View Code

    如果并发的多个任务是I/O密集型:多线程效率高

    #如果并发的多个任务是I/O密集型:多线程效率高
    from multiprocessing import Process
    from threading import Thread
    import threading
    import os,time
    def work():
        time.sleep(2)#类似i/o
        # print('===>')
    
    if __name__ == '__main__':
        l=[]
        print(os.cpu_count()) #本机为4核
        start=time.time()
        for i in range(400):
            p=Process(target=work) #耗时14s多,大部分时间耗费在创建进程上,
            #p=Thread(target=work) #耗时2s多
            l.append(p)
            p.start()
        for p in l:
            p.join()
        stop=time.time()
        print('run time is %s' %(stop-start))
    View Code

    二、死锁与递归锁

    1、死锁现象

    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

    此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

    from threading import Thread,Lock
    import time
    mutexA=Lock()
    mutexB=Lock()
    
    class MyThread(Thread):
        def run(self):
            self.func1()
            self.func2()
        def func1(self):
            mutexA.acquire()
            print('33[41m%s 拿到A锁33[0m' %self.name)
    
            mutexB.acquire()
            print('33[42m%s 拿到B锁33[0m' %self.name)
            mutexB.release()
    
            mutexA.release()
    
        def func2(self):
            mutexB.acquire()
            print('33[43m%s 拿到B锁33[0m' %self.name)
            time.sleep(2)
    
            mutexA.acquire()
            print('33[44m%s 拿到A锁33[0m' %self.name)
            mutexA.release()
    
            mutexB.release()
    
    if __name__ == '__main__':
        for i in range(10):
            t=MyThread()
            t.start()
    执行效果
    
    Thread-1 拿到A锁
    Thread-1 拿到B锁
    Thread-1 拿到B锁
    Thread-2 拿到A锁 #出现死锁,整个程序阻塞住
    
    Thread-1 拿到B锁后要去拿A锁,但A所在Thread-2手上
    Thread-2 拿到A锁后要去拿B锁,但B锁在Thread-1手上
    View Code

    2、死锁的解决办法---递归锁

    递归锁:可以连续acquire多次,每acquire一次计数器+1,只有计数为0时,才能被抢到

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

    这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。

    直到一个线程所有的acquire都被release,其他的线程才能获得资源。

    上面的例子如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次

    # 递归锁:可以连续acquire多次,每acquire一次计数器+1,只有计数为0时,才能被抢到acquire
    from threading import Thread,RLock
    import time
    
    mutexB=mutexA=RLock()
    
    class MyThread(Thread):
        def run(self):
            self.f1()
            self.f2()
    
        def f1(self):
            mutexA.acquire()
            print('%s 拿到了A锁' %self.name)
    
            mutexB.acquire()
            # 此时acquire 计数器为2
            print('%s 拿到了B锁' %self.name)
            mutexB.release()
    
            mutexA.release()
            # 此时acquire计数器为0 这样其他的线程才可以抢锁
    
        def f2(self):
            mutexB.acquire()
            print('%s 拿到了B锁' % self.name)
            time.sleep(1)
    
            mutexA.acquire()
            print('%s 拿到了A锁' % self.name)
            mutexA.release()
    
            mutexB.release()
    
    if __name__ == '__main__':
        for i in range(10):
            t=MyThread()
            t.start()
    
    Thread-1 拿到了A锁
    Thread-1 拿到了B锁
    Thread-1 拿到了B锁
    Thread-1 拿到了A锁
    Thread-2 拿到了A锁
    Thread-2 拿到了B锁
    Thread-2 拿到了B锁
    Thread-2 拿到了A锁
    Thread-4 拿到了A锁
    Thread-4 拿到了B锁
    Thread-4 拿到了B锁
    Thread-4 拿到了A锁
    Thread-6 拿到了A锁
    Thread-6 拿到了B锁
    Thread-6 拿到了B锁
    Thread-6 拿到了A锁
    Thread-8 拿到了A锁
    Thread-8 拿到了B锁
    Thread-8 拿到了B锁
    Thread-8 拿到了A锁
    Thread-10 拿到了A锁
    Thread-10 拿到了B锁
    Thread-10 拿到了B锁
    Thread-10 拿到了A锁
    Thread-5 拿到了A锁
    Thread-5 拿到了B锁
    Thread-5 拿到了B锁
    Thread-5 拿到了A锁
    Thread-9 拿到了A锁
    Thread-9 拿到了B锁
    Thread-9 拿到了B锁
    Thread-9 拿到了A锁
    Thread-7 拿到了A锁
    Thread-7 拿到了B锁
    Thread-7 拿到了B锁
    Thread-7 拿到了A锁
    Thread-3 拿到了A锁
    Thread-3 拿到了B锁
    Thread-3 拿到了B锁
    Thread-3 拿到了A锁
    View Code
  • 相关阅读:
    Hadoop分布式文件系统:架构和设计
    分布式设计学习资料
    codeforces上一道贪心算法题
    优先队列实现n路归并算法O(n * lgK)
    LINUX 暂停、继续进程
    重叠(Overlapped)IO模型
    WSAEventSelect模型
    WSAEventSelect模型 应用实例,重写TCP服务器实例
    选择模型2
    第四章 数据抽象 《C++编程思想》
  • 原文地址:https://www.cnblogs.com/foremostxl/p/9734439.html
Copyright © 2020-2023  润新知