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