• GIL解释器,协程,gevent模块


    GIL解释器锁

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

    首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

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

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

    每一个cpython进程内都有一个GIL
    GIL导致同一进程内的多个线程同一时间只能有一个运行
    之所以有GIL,是因为cpython的内存管理不是线程安全的
    对于计算密集型用多进程,对于IO密集型用多线程

    死锁和递归锁

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

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

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

    复制代码
    from threading import Thread,Lock,RLock
    import time
    # mutexA = Lock()
    # mutexB = Lock()
    mutexA = mutexB = RLock()  # 一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
    
    class MyThread(Thread):
        def run(self):
            self.f1()
            self.f2()
    
        def f1(self):
            mutexA.acquire()
            print('%s 拿到A锁'%self.name)
            mutexB.acquire()
            print('%s 拿到A锁' % self.name)
            mutexB.release()
            mutexA.release()
        def f2(self):
            mutexB.acquire()
            print('%s 拿到B锁'%self.name)
            time.sleep(0.1)
            mutexA.acquire()
            print('%s 拿到A锁' % self.name)
            mutexA.release()
            mutexB.release()
    
    if __name__ == '__main__':
        for i in range(10):
            t = MyThread()
            t.start()
    复制代码

    协程

    协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的

    利用yield实现单线程下的并发

    复制代码
    import time
    def consumer():
        '''任务1:接收数据,处理数据'''
        while True:
            x=yield
    
    def producer():
        '''任务2:生产数据'''
        g = consumer()
        next(g)
        for i in range(10000000):
            g.send(i)
    
    start = time.time()
    # 基于yield保存状态,实现两个任务直接来回切换,即并发的效果
    # PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的.
    producer()
    
    stop = time.time()
    print(stop-start)  # 2.0272178649902344
    复制代码

    gevent模块

    time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了

    from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前

    或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

    复制代码
    # pip3 install gevent
    # 1.切换+保存状态
    # 2.检测IO,实现遇到IO切换
    from gevent import monkey
    monkey.patch_all()  # 将程序中的所有IO操作打标记,使gevent能识别
    import gevent
    import time
    
    def eat(name):
        print('%s eat 1'%name)
        time.sleep(2)
        print('%s eat 2' % name)
    def play(name):
        print('%s play 1'%name)
        time.sleep(3)
        print('%s play 2' % name)
    
    g1 = gevent.spawn(eat,'alex')
    g2 = gevent.spawn(play,'egon')
    
    g1.join()
    g2.join()
    复制代码
  • 相关阅读:
    FastDFS 安装与使用
    leecode刷题(18)-- 报数
    时间戳转换日期格式
    嵌入式Linux的FTP服务端软件(stupid-ftpd)
    iMx280A测试声纹
    Linux 版本查询
    Linux下的目录结构
    uboot主Makefile分析
    uboot配置过程详解1
    路由器设置
  • 原文地址:https://www.cnblogs.com/QQ279366/p/7988349.html
Copyright © 2020-2023  润新知