python-协程

协程,又称微线程,纤程。英文名Coroutine。

线程是系统级别的它们由操作系统调度,而协程则是程序级别的由程序根据需要自己调度。在一个线程中会有很多函数,我们把这些函数称为子程序,在子程序执行过程中可以中断去执行别的子程序,而别的子程序也可以中断回来继续执行之前的子程序,这个过程就称为协程。也就是说在同一线程内一段代码在执行过程中会中断然后跳转执行别的代码,接着在之前中断的地方继续开始执行,类似与yield操作。

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,也就是知道属于自己的变量,这就是为什么生成器在yield之后,可以继续使用yield 的变量的原因。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

协程的优点:

  (1)无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力)

  (2)无需原子操作锁定及同步的开销

  (3)方便切换控制流,简化编程模型

  (4)高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

协程的缺点:

  (1)无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。

  (2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

接下来是用协程实现 生产者,消费者的经典案例:

def consumer():
    print("[consumer]:i am hunger , i need food")
    while True:
        food = yield
        if food is None:
            break
        print(f"[consumer]: so delicious {food} baozi")
    print('i see')


def producter():
    g = consumer() #生成一个消费者对象
    i = 0
    next(g) #协程必须预激活,走到 上面yield处,然后挂着,等待调用方给它发消息
    while i < 5:
        print(f"[producter]: make food {i} baozi")
        g.send(i) #使用生成器提供的api send,send中的值就是上面food的值;
        i += 1
    print("[producter]: oh no, i am tried")
    g.close() #关闭协程,养成良好习惯


producter()
[consumer]:i am hunger , i need food
[producter]: make food 0 baozi
[consumer]: so delicious 0 baozi
[producter]: make food 1 baozi
[consumer]: so delicious 1 baozi
[producter]: make food 2 baozi
[consumer]: so delicious 2 baozi
[producter]: make food 3 baozi
[consumer]: so delicious 3 baozi
[producter]: make food 4 baozi
[consumer]: so delicious 4 baozi
[producter]: oh no, i am tried

值得注意点是:就算不用调用上面的g.close(),程序虽然会正常退出,但是这个协程并没有关闭,而且它的此时的状态是:GEN_SUSPENDED ;

>>> from demo_product import consumer
>>> from inspect import getgeneratorstate
>>> g = consumer()
>>> getgeneratorstate(g)
'GEN_CREATED'
>>> next(g)
[consumer]:i am hunger , i need food
>>> getgeneratorstate(g)
'GEN_SUSPENDED'
>>> g.send(2)
[consumer]: so delicious 2 baozi
>>> g.send(5)
[consumer]: so delicious 5 baozi
>>> g.send(None)
i see
Traceback (most recent call last):
  File "", line 1, in 
StopIteration
>>> getgeneratorstate(g)
'GEN_CLOSED'
>>> g = consumer()
>>> next(g)
[consumer]:i am hunger , i need food
>>> g.close()
>>> getgeneratorstate(g)
'GEN_CLOSED'

从上面的代码中,我们可以发现,协程对象是必须要预激活的,当协程激活后,协程在yield处停留。此时可以使用三种方式结束协程,一种是通过g.send(None)发送一个None值,然后得到异常StopIteration,另一种方式是通过g.close(),这种方式相比来说比较优雅。还有一种方式就是通过g.throw(error),主动抛出一个异常,类型可以自定;倘若 是想要协程返回值,那么就需要使用g.send(None)以及g.throw(error)这两种方式了。

下方是通过g.send(None)的方式,结束协程以及获取协程的返回值;

def consumer():
    print("[consumer]:i am hunger , i need food")
    while True:
        food = yield
        if food is None:
            break
        print(f"[consumer]: so delicious {food} baozi")
    return "i see"


def producter():
    g = consumer()
    i = 0
    next(g)
    while i < 5:
        print(f"[producter]: make food {i} baozi")
        g.send(i)
        i += 1
    print("[producter]: oh no, i am tried")
    try:
        g.send(None)
    except StopIteration as e:
        res = e.value
    print(res)

if __name__ == "__main__":
    producter()

其实python还提供一种更简单的方式获取协程的返回值,那就是使用关键字:yield from ,通过这种方式可以优雅的处理StopIteration的错误,并且获取错误中的返回值,不过这种方式得在委派生成器中使用,通过委派生成器连接调用方与子生成器,这个委派生成器则是两则沟通的桥梁,具体细节我不在此多说,有兴趣的可以深入了解;下面是yield from基础演示,它省去了一层for循环。

>>> def gen():
...     for i in range(3):
...             yield i
...
>>> g = gen()
>>> for i in g:
...     print(i)
...
0
1
2
>>> def gen():
...     yield from range(3)
...
>>> g = gen()
>>> for i in g:
...     print(i)
...
0
1
2
>>>

总结:可见协程在实现异步上是多么方便,cpu只需要发出指令集,等待返回结果即可,在这等待的时间内,cpu可以去干其他活,极大的提高了效率。正如《优化python代码的59个建议》中提到的,第40条,应该通过使用协程进行并发操作。

你可能感兴趣的:(python3)