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()
复制代码
posted @ 2017-12-05 17:27  邯城吴彦祖  阅读(531)  评论(0编辑  收藏  举报