📜  Python中的协程

📅  最后修改于: 2020-04-07 05:30:26             🧑  作者: Mango

先决条件:生成器
我们都熟悉函数,也称为子例程过程子过程等。函数是一系列指令,打包成一个单元来执行特定任务。如果将复杂功能的逻辑分为几个独立的步骤,这些步骤本身就是函数,则这些函数称为辅助函数或子例程
Python中的子例程由main函数调用,该函数负责协调这些子例程的使用。子例程具有单个入口点。

协程是子例程的泛化。它们用于协作式多任务处理,在协作式多任务处理中,process会定期或在空闲时自动产生(或放弃)控制,以使多个应用程序能够同时运行。协程和子例程之间的区别是:

  • 与子例程不同,协程具有许多用于挂起和恢复执行的入口点。协程可以暂停执行并将控制权转移给其他协程,并且可以从中断处重新开始执行。
  • 与子例程不同,没有主函数可以按特定顺序调用协程并协调结果。协同程序是协作的,这意味着它们链接在一起以形成管道。一个协程可能会消耗输入数据并将其发送给其他处理程序, 最后可能会有协程显示结果。

协程与线程

现在您可能正在考虑协程与线程有何不同,两者似乎都可以完成相同的工作。
如果是线程,则是操作系统(或运行时环境)根据调度程序在线程之间切换。在协程的情况下,是由程序员和编程语言来决定何时切换协程。协同程序通过程序员在设定点处挂起和恢复来协同工作多个任务。

Python协程

在Python中,协程与生成器相似,但是协程很少,并且我们使用yield语句的方式略有变化。生成器产生用于迭代的数据,而协程也可以消耗数据。
在Python 2.5中,对yield语句进行了一些修改,现在yield也可以用作expression。例如,在赋值语句的右侧:

line = (yield)

我们发送给协程的任何值都被(yield)表达式捕获并返回。
可以通过send()方法将值发送到协程。例如,考虑如下协程,该协程打印出带有前缀“ Dear”的名称。我们将使用send()方法将名称发送到协程。

# Python3展示协程
def print_name(prefix):
    print("搜索前缀:{}".format(prefix))
    while True:
        name = (yield)
        if prefix in name:
            print(name)
# 调用协程, 什么也不会发生
corou = print_name("Dear")
# 这回开始执行协程
# 打印第一行‘搜索前缀’
# 并一直执行到第一个yield表达式
corou.__next__()
# 发出输出
corou.send("mango")
corou.send("Dear mango")

输出:

搜索前缀:Dear
Dear mango

协程的执行

协程的执行类似于生成器。当我们调用协程时,什么也没有发生,它仅在响应next()send()方法时运行。在上面的示例中可以清楚地看到这一点,因为只有在调用__next __()方法之后,协程才开始执行。调用之后,执行前进到第一个yield表达式,现在执行暂停并等待将值发送到协程对象。当第一个值发送给它时,它将检查前缀和打印名称(如果存在前缀)。打印名称后,它将经历循环,直到再次遇到name =(yield)表达式为止。

关闭协程

协程可能会无限期运行,关闭协程使用close()方法。当协程关闭时,它会生成GeneratorExit异常,可以按常规方式捕获该异常。关闭协程后,如果我们尝试发送值,它将引发StopIteration异常。以下是一个简单的示例:

# Python3展示关闭协程
def print_name(prefix):
    print("搜索前缀:{}".format(prefix))
    try :
        while True:
                name = (yield)
                if prefix in name:
                    print(name)
    except GeneratorExit:
            print("关闭协程!!")
corou = print_name("Dear")
corou.__next__()
corou.send("mango")
corou.send("Dear mango")
corou.close()

输出:

搜索前缀:Dear
Dear mango
关闭协程!!

链接协程以创建管道

协程可以用来设置管道。我们可以将协程链接在一起,并使用send()方法通过管道推送数据。管道需要:

    • 派生整个管道的初始来源(生成器)。生成器通常不是协程,这只是一种简单的方法。
    • 一个接收器,其是管道的终点。接收器可能会收集并显示所有数据。

以下是链接的一个简单示例:


# Python3展示链接协程
def producer(sentence, next_coroutine):
    '''
    Producer把string分开,送给pattern_filter
    '''
    tokens = sentence.split(" ")
    for token in tokens:
        next_coroutine.send(token)
    next_coroutine.close()
def pattern_filter(pattern="ing", next_coroutine=None):
    '''
    在接收到的令牌中搜索模式,如果模式匹配,则将其发送到print_token()协程进行打印 
    '''
    print("搜索 {}".format(pattern))
    try:
        while True:
            token = (yield)
            if pattern in token:
                next_coroutine.send(token)
    except GeneratorExit:
        print("过滤完毕!!")
def print_token():
    '''
    充当接收器,只需打印接收到的令牌
    '''
    print("这是一个水槽,会打印tokens")
    try:
        while True:
            token = (yield)
            print(token)
    except GeneratorExit:
        print("打印完毕!")
pt = print_token()
pt.__next__()
pf = pattern_filter(next_coroutine = pt)
pf.__next__()
sentence = "Bob is running behind a fast moving car"
producer(sentence, pf)

输出:

这是一个水槽,会打印tokens
搜索
running
moving
过滤完毕!!
打印完毕!