📜  Python中的协程

📅  最后修改于: 2022-05-13 01:54:25.032000             🧑  作者: Mango

Python中的协程

先决条件:发电机
我们都熟悉函数,也称为子例程过程子过程等。函数是打包为一个单元以执行特定任务的指令序列。当一个复杂函数的逻辑被分成几个独立的步骤时,这些步骤本身就是函数,那么这些函数被称为辅助函数或子程序

Python中的子程序由负责协调这些子程序的使用的主函数调用。子程序只有一个入口点。

子程序

协程是子程序的泛化。它们用于协作多任务处理,其中一个进程定期或在空闲时自愿放弃(放弃)控制权,以使多个应用程序能够同时运行。协程和子程序的区别是:

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

协程

协程与线程

现在您可能在想协程与线程有何不同,两者似乎都在做同样的工作。
在线程的情况下,它是根据调度程序在线程之间切换的操作系统(或运行时环境)。而在协程的情况下,决定何时切换协程的是程序员和编程语言。协程通过程序员在设定点暂停和恢复来协同工作多任务。

Python协程

在Python中,协程类似于生成器,但几乎没有额外的方法,并且我们使用 yield 语句的方式略有变化。生成器为迭代生成数据,而协程也可以使用数据。
在Python 2.5 中,引入了对 yield 语句的轻微修改,现在 yield 也可以用作表达式。例如在作业的右侧——

line = (yield)

我们发送给协程的任何值都会被(yield)表达式捕获并返回。

可以通过send()方法将值发送到协程。例如,考虑这个协程,它打印出带有前缀“Dear”的名称。我们将使用 send() 方法将名称发送到协程。

Python3
# Python3 program for demonstrating
# coroutine execution
 
def print_name(prefix):
    print("Searching prefix:{}".format(prefix))
    while True:
        name = (yield)
        if prefix in name:
            print(name)
 
# calling coroutine, nothing will happen
corou = print_name("Dear")
 
# This will start execution of coroutine and
# Prints first line "Searching prefix..."
# and advance execution to the first yield expression
corou.__next__()
 
# sending inputs
corou.send("Atul")
corou.send("Dear Atul")


Python3
# Python3 program for demonstrating
# closing a coroutine
 
def print_name(prefix):
    print("Searching prefix:{}".format(prefix))
    try :
        while True:
                name = (yield)
                if prefix in name:
                    print(name)
    except GeneratorExit:
            print("Closing coroutine!!")
 
corou = print_name("Dear")
corou.__next__()
corou.send("Atul")
corou.send("Dear Atul")
corou.close()


Python3
# Python3 program for demonstrating
# coroutine chaining
 
def producer(sentence, next_coroutine):
    '''
    Producer which just split strings and
    feed it to pattern_filter coroutine
    '''
    tokens = sentence.split(" ")
    for token in tokens:
        next_coroutine.send(token)
    next_coroutine.close()
 
def pattern_filter(pattern="ing", next_coroutine=None):
    '''
    Search for pattern in received token
    and if pattern got matched, send it to
    print_token() coroutine for printing
    '''
    print("Searching for {}".format(pattern))
    try:
        while True:
            token = (yield)
            if pattern in token:
                next_coroutine.send(token)
    except GeneratorExit:
        print("Done with filtering!!")
 
def print_token():
    '''
    Act as a sink, simply print the
    received tokens
    '''
    print("I'm sink, i'll print tokens")
    try:
        while True:
            token = (yield)
            print(token)
    except GeneratorExit:
        print("Done with printing!")
 
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)


输出:

Searching prefix:Dear
Dear Atul

协程的执行

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

关闭协程

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

Python3

# Python3 program for demonstrating
# closing a coroutine
 
def print_name(prefix):
    print("Searching prefix:{}".format(prefix))
    try :
        while True:
                name = (yield)
                if prefix in name:
                    print(name)
    except GeneratorExit:
            print("Closing coroutine!!")
 
corou = print_name("Dear")
corou.__next__()
corou.send("Atul")
corou.send("Dear Atul")
corou.close()

输出:

Searching prefix:Dear
Dear Atul
Closing coroutine!!

链接协程以创建管道

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

  • 初始源(生产者)派生整个管道。生产者通常不是协程,它只是一个简单的方法。
  • 一个sink ,它是管道的端点。接收器可能会收集所有数据并显示它。

管道

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


Python3

# Python3 program for demonstrating
# coroutine chaining
 
def producer(sentence, next_coroutine):
    '''
    Producer which just split strings and
    feed it to pattern_filter coroutine
    '''
    tokens = sentence.split(" ")
    for token in tokens:
        next_coroutine.send(token)
    next_coroutine.close()
 
def pattern_filter(pattern="ing", next_coroutine=None):
    '''
    Search for pattern in received token
    and if pattern got matched, send it to
    print_token() coroutine for printing
    '''
    print("Searching for {}".format(pattern))
    try:
        while True:
            token = (yield)
            if pattern in token:
                next_coroutine.send(token)
    except GeneratorExit:
        print("Done with filtering!!")
 
def print_token():
    '''
    Act as a sink, simply print the
    received tokens
    '''
    print("I'm sink, i'll print tokens")
    try:
        while True:
            token = (yield)
            print(token)
    except GeneratorExit:
        print("Done with printing!")
 
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)

输出:

I'm sink, i'll print tokens
Searching for ing
running
moving
Done with filtering!!
Done with printing!

参考

  • http://www.dabeaz.com/coroutines/Coroutines.pdf
  • https://en.wikipedia.org/wiki/Coroutine