协程(coroutine),又称微线程,纤程,是一种用户级的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其他协程共享全局数据和其他资源。
协程需要用户自己编写调度逻辑,对于 CPU 来说,协程其实是单线程,CPU 不需要考虑怎么去调度、切换上下文,这样就省去了 CPU 的切换开销,因此协程在一定程度上有好于多线程。
1. python 实现协程
第三方库 gevent 提供了比较完善的协程支持,gevent 是一个基于协程的 python 网络数据库,使用 greenlet 在 libev 事件循环顶部提供了一个有高级别并发性的 API。主要特性有以下几点。
基于 libev 的快速事件循环,Linux 上是 epoll 机制。
基于 greenlet 的轻量级执行单元。
API 复用了 python 标准库里的内容。
支持 SSL 的协作式 sockets。
可通过线程池或 c-ares 实现 DNS 查询。
通过 monkey patching 功能使得第三方模块变成协作式。
gevent 对协程的支持,本质上是 greenlet 在实现切换工作。greenlet 工作流程如下:假如进行访问网络的 IO 操作,出现阻塞,greenlet 就显式切换到另一段没有阻塞的代码段执行,直到原先的阻塞状况消失以后,再自动切换回原来的代码段继续处理。因此,greenlet 是一种合理安排的串行方式。
由于 IO 操作非常耗时,经常使程序处于等待状态,有了 greenlet 自动切换协程,保证总有 greenlet 在运行,而不是等待 IO,这就是协程一般比多线程效率高的原因。由于切换在 IO 操作时自动完成,所以 gevent 需要修改 python 自带的一些标准库,将一些常见的阻塞,如 socket、select 等地方实现协程跳转,这一过程在启动时通过 monkey patch 完成。
以上程序主要使用 gevent 中的 spawn 方法和 joinall 方法。spawn 方法是用来形成协程,joinall 方法是添加这些协程任务,并且启动运行。从结果可以看出,3 个网络操作是并发执行的,运行结束顺序不同,但其实只有一个线程。
2. 协程中池的实现
gevent 中还提供了对池的支持。当拥有动态数量的 gevent 需要进行并发管理时,如限制并发数,可以使用池来实现,这在处理大量的网络和 IO 操作时是非常需要的。
运行结果可看出,Pool 对象实现了对协程的并发数量管理,代码中规定协程中的池容量为 2,因此程序是先执行前两个任务,当其中一个任务完成是,才会执行第三个任务,达到了限制并发数的目的。
领取专属 10元无门槛券
私享最新 技术干货