关于python3:python协程

6次阅读

共计 3138 个字符,预计需要花费 8 分钟才能阅读完成。

协程,又称微线程,纤程。英文名 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 "<stdin>", line 1, in <module>
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 条,应该通过应用协程进行并发操作。

正文完
 0