1. 协程是什么?
协程,英文名Coroutine。它是一种能够让程序暂停执行并能够恢复执行的机制。可以将一个任务分为多个子任务,然后在任务之间来回切换,最终完成整个任务。协程可以让程序在执行io密集型任务时,不必等到io请求返回,而是可以先切换到其它任务,这样可以让CPU的利用率更高。
1.1 协程的特点
协程相对于线程和进程的特点:
不会发生线程/进程的上下文切换,更加轻量级;
不需要和操作系统直接打交道,可以自己控制任务的切换;
具有极高的执行效率和极好的扩展性。
2. Python 实现协程的方法
Python有很多实现协程的方法,其中比较典型的有:
生成器(generator);
使用 async 和 await 关键字;
使用第三方库 gevent。
2.1 生成器实现协程
在Python 2.x中,生成器还没有定义 coroutine 类型,但是在 3.x中,可以使用 coroutine 类型并定义一个__await__()方法。这个方法返回自身,也就是生成器对象。因此,可以使用生成器来实现协程。
def subgen():
while True:
x = yield
print(x)
def coroutine(func):
def start(*args, **kwargs):
cr = func(*args, **kwargs)
next(cr)
return cr
return start
@coroutine
def average():
count = 0
sum = 0
average = None
while True: # 子生成器
x = yield average
count += 1
sum += x
average = round(sum / count, 2)
avg = average()
subgen1 = subgen()
subgen2 = subgen()
next(avg)
subgen1.send(10)
subgen1.send(20)
subgen2.send(30)
print(avg.send(None))
print(avg.send(40))
打印输出如下:
None
15.0
25.0
35.0
在这个示例中,使用生成器定义了两个子生成器,即 subgen1 和 subgen2。然后,定义了一个协程 average,将其中包含的主逻辑简化为了维护一个总和 sum 和一个计数器 count,并求出平均值。下一步,应该调用 subgen1 和 subgen2,向其传递参数,并打印平均值。
2.2 async 和 await 实现协程
Python3.5 引入的关键字 async 和 await,它们被设计成更加简便的编写异步代码的方式,它们也是实现协程的另一种方式。
async定义的函数就是一个协程,await表明一个协程可以等待另一个协程执行完毕。
import time
async def task1():
print('Start task1')
await asyncio.sleep(1) # 模拟io操作
print('Finish task1')
async def task2():
print('Start task2')
await asyncio.sleep(0.5) # 模拟io操作
print('Finish task2')
async def main():
start_time = time.time()
await task1()
await task2()
end_time = time.time()
print(f'Time interval: {end_time - start_time:.2f} seconds')
asyncio.run(main())
运行结果如下,可以看到,task1 和 task2 几乎是同时执行的,而不是像多线程那样一个一个执行。
Start task1
Start task2
Finish task2
Finish task1
Time interval: 1.50 seconds
2.3 gevent 实现协程
使用第三方库 gevent 可以让Python应用程序轻松地实现协程,可以使用 gevent.spawn、gevent.joinall 等函数在程序中创建协程。
import gevent
import time
def task1():
print('Start task1')
gevent.sleep(1) # 因为 gevent 提供的是微线程(greenlet),因此这里是使用 gevent.sleep 进行协程切换,而不是 time.sleep
print('Finish task1')
def task2():
print('Start task2')
gevent.sleep(0.5)
print('Finish task2')
def main():
start_time = time.time()
gevent.joinall([gevent.spawn(task1), gevent.spawn(task2)]) # 传入协程对象列表,使用 joinall 进行等待
end_time = time.time()
print(f'Time interval: {end_time - start_time:.2f} seconds')
if __name__ == '__main__':
main()
运行结果与使用 async 和 await 的代码示例基本一样。
3. Python协程的应用场景
协程相对于多线程的最主要优点是有更高的执行效率,同时避免了多线程中的死锁、竞态条件等问题。因此,Python协程广泛应用于以下场景:
网络爬虫:由于网络请求的io操作非常密集,使用协程可以有效利用io等待的时间,提高数据采集效率;
Web框架:例如 Tornado 的协程异步处理方式可以让请求处理更加高效;
大量IO操作:能够避免由于IO等待导致的长时间等待问题。
结语
协程作为一种可以暂停执行并能够恢复执行的机制,可以让程序在执行io密集型任务时不必等到io请求返回,更好地利用CPU资源。Python提供了多种协程实现的方式,包括生成器、async/await 关键字、第三方库 gevent等。协程广泛应用于网络爬虫、Web框架、大量IO操作等场景。希望本文能够帮助你理解Python协程的基本概念和应用。