Python3.x:协程

协程,又称微线程,是一种用户态的轻量级线程

协程拥有自己的寄存器上下文和栈,在上下文切换的时候不需要占用CPU(线程上下文切换占用CPU)

协程的好处:

  • 无需线程上下文切换时的开销,
  • 无需原子操作锁定及同步的开销
  • 方便切换控制流,简化编程模型
  • 高并发+高拓展性+低成本

缺点:

  • 无法利用多核资源
  • 进行阻塞操作时会阻塞整个程序

明白了它的原理,就能明白它的这些优缺点:
协程的本质还是一条线程,但是一条线程中可以开启上千上万个协程,协程上下文切换有自己的储存空间,所以不占用CPU,每次遇到IO操作,程序就会进行上下文的切换,当IO操作完成, 上下文又会切换回来,所以效率也很高,因为归根结底还是一个线程,所以无法利用多核资源,但是和进程配合就可以了,另外,因为是单线程,所以阻塞的时候程序会停止运行

先看一个使用yield实现协程操作的例子

# 生成器
def consumer(name):
    print('start eating baozi...')
    while True:
        new_baozi = yield
        print('[%s] is eating baozi %s' % (name, new_baozi))


def producer():
    # 执行生成器中的代码
    r = con.__next__()
    r = con2.__next__()
    n = 0
    while n < 5:
        n += 1
        # send的作用恢复生成器的同时传送一个值,这里指的就是new_baozi
        con.send(n)
        con2.send(n)
        print('\033[32;1m[producer]\033[0m is making baozi %s' % n)


if __name__ == '__main__':
    con = consumer('c1')
    con2 = consumer('c2')
    p = producer()

两个生成器,使用send方法执行生成器剩下的代码和恢复生成器,这里只是模拟一下协程的工作方式,并不是标准的协程, 下面用greenlet来手动模拟一下协程

from greenlet import greenlet         
                                      
                                      
def test1():                          
    print(1)                          
    # 跳到test2打印2                      
    gr2.switch()                      
    print(3)                          
    # 跳到test2打印4                      
    gr2.switch()                      
                                      
                                      
def test2():                          
    print(2)                          
    # 跳到test1打印3                      
    gr1.switch()                      
    print(4)                          
                                      
                                      
gr1 = greenlet(test1)                 
gr2 = greenlet(test2)                 
# 跳到test1打印1                          
gr1.switch()                          
Python3.x:协程_第1张图片

这里的greenlet实现了一个线程中上下文的切换的功能,但是还是没有真正的实现协程,因为还没有实现遇到IO操作就切换,下面就用到了gevent,而gevent就封装了greenlet,所以说你安装了gevent就可以使用greenlet了

Gevent是一个第三方库,可以轻松通过gevent实现并发同步或者异步编程,在gevent中用到的主要模式是Greenlet,它是以C扩展模块形式嵌入Python的轻量级协议,Greenlet会全部运行在主程序操作系统进程的内部,协作式调度

先看看用法

import gevent


def func1():
    print('\033[31;1m我是func1里的\033[0m')
    # IO操作
    gevent.sleep(2)
    print('\033[31;1m睡完了\033[0m')


def func2():
    print('\033[32;1m我是func2里的\033[0m')
    # IO操作
    gevent.sleep(1)
    print('\033[32;1m睡完了\033[0m')


# 列表形式传入joinall方法
gevent.joinall([
    # spawn 产卵 传入函数名
    gevent.spawn(func1),
    gevent.spawn(func2),

])
Python3.x:协程_第2张图片

通过这段可以看出程序是并发执行的,从输出可以看出来,当然自己跑一下会更明显,func2里sleep时间短,所以会先‘睡完’,这样就实现了智能的遇到IO操作就切换

可以跑一下这段代码体会一下协程的高效

import gevent


def task(pid):
    """
    Some task
    """
    gevent.sleep(0.5)
    print('Task %s done' % pid)

# 串行
def test1():
    for i in range(1, 10):
        task(i)

# 并发
def test2():
    threads = [gevent.spawn(task, i) for i in range(10)]
    gevent.joinall(threads)


print('test1:')
test1()

print('test2:')
test2()

但是啊,gevent有的时候跟一些库配合的时候会检测不到他们的IO操作,所以这个时候就要使用gevent库的一些额外功能了,例如:

from gevent import monkey
import gevent
from urllib.request import urlopen
# 检测一些原先检测不到的IO操作
monkey.patch_all()


def f(url):
    print('GET: %s' % url)
    resp = urlopen(url)
    data = resp.read()
    print('%d bytes received from %s.' % (len(data), url))


gevent.joinall([
    gevent.spawn(f, 'https://www.python.org/'),
    gevent.spawn(f, 'https://www.yahoo.com/'),
    gevent.spawn(f, 'https://github.com/'),
])

使用了gevent自带的monkey.patch_all()就可以检测到这些库的IO操作了

转载请注明出处

python自学技术互助扣扣群:670402334

你可能感兴趣的:(Python3.x:协程)