协程(coroutine)是一种用户空间的轻量级线程,也叫微线程。与线程不同,协程并不需要线程的上下文切换开销,因此协程的运行效率更高。Python作为一门支持协程的语言,从2.x版本开始就提供了yield关键字,从3.5版本开始提供了async/await关键字,使得协程在Python中得到了广泛应用。
协程的概念最早由Melvin Conway提出,用于解决多任务协作的问题。协程是一种特殊的函数,它可以在执行过程中暂停,将控制权交给调用者,等待下一次调用时从暂停的地方继续执行。这种方式不需要像线程一样将控制权交给操作系统内核,而是由程序员自己控制,因此协程的效率更高。
在Python中,协程的实现有两种方式:
在Python2.x版本中,协程的实现是通过生成器实现的。生成器是一种特殊的函数,当函数被调用时,它不会立即执行,而是返回一个生成器对象。生成器对象可以使用next()函数或send()方法向生成器函数中传递值,从而控制生成器函数的执行。
协程的实现基于生成器的yield关键字。当在生成器函数中使用yield关键字时,生成器函数会在yield处暂停,并将yield后面的值返回给调用者。当生成器函数再次被调用时,它会从yield处继续执行,直到再次遇到yield时再次暂停,并将yield后面的值返回给调用者。这样就形成了一种类似于线程的协作式多任务模型。
下面是一个使用生成器实现协程的例子:
def coro():
while True:
x = yield
print('Received:', x)
c = coro()
next(c) # 首次调用时必须使用next函数,让生成器到达第一个yield处
c.send(1)
c.send(2)
输出结果为:
Received: 1
Received: 2
在这个例子中,coro()函数是一个生成器函数,使用yield关键字实现了协程的暂停和恢复。在协程函数中使用x = yield
语句时,生成器会在这里暂停,并将控制权交给调用者。调用者可以使用send()
方法向协程函数中传递值,并使得协程函数从yield
语句处继续执行。
需要注意的是,协程函数在首次调用时必须使用next()
函数,让生成器到达第一个yield
处,否则会抛出StopIteration
异常。
Python3.5版本引入了async/await
关键字,为协程的实现带来了新的方式。相比于生成器实现的协程,async/await
关键字更加直观、易用。
使用async
关键字修饰的函数被称为异步函数,其内部可以使用await
关键字暂停异步函数的执行,并等待异步操作完成后再次恢复执行。异步函数可以返回一个协程对象,协程对象可以被事件循环对象调度执行。
下面是一个使用async/await
实现协程的例子:
import asyncio
async def coro():
while True:
x = await asyncio.sleep(1) # 通过await关键字暂停异步函数的执行
print('Received:', x)
loop = asyncio.get_event_loop()
loop.create_task(coro())
loop.run_forever()
在这个例子中,coro()
函数被修饰为一个异步函数,使用await
关键字暂停异步函数的执行,并等待异步操作完成后再次恢复执行。在这里使用了asyncio.sleep(1)
函数模拟了一个异步操作,每隔1秒钟执行一次。通过loop.create_task()
方法将协程对象添加到事件循环中,并使用loop.run_forever()
方法启动事件循环。
需要注意的是,在使用async/await
实现协程时,必须在异步函数中使用异步操作,否则无法发挥协程的优势。
总结
协程是一种轻量级的线程,比线程更加高效。Python提供了两种实现协程的方式,分别是使用生成器实现的协程和使用async/await
实现的协程。使用生成器实现的协程需要手动控制协程的状态,而使用async/await
实现的协程则更加简单直观。无论使用哪种方式,协程都是一种非常有用的编程技术,可以用于解决多任务协作的问题。