协程:又称微线程,在单线程上执行多个任务,用函数切换,开销极小。不通过操作系统调度,没有进程、线程的切换开销。genvent,monkey.patchall
多线程请求返回是无序的,那个线程有数据返回就处理那个线程,而协程返回的数据是有序的。
缺陷:单线程执行,处理密集CPU和本地磁盘IO的时候,性能较低。处理网络I/O性能还是比较高.
使用gevent 或者是python3.6 及以上的asyncio
问题: 什么场景下使用协程反而会更差的性能?
1 | def consumer(): |
新的写法
1 | import asyncio |
协程不是趋势,它是一个在历史中被挖掘出来的、对现有问题的一个有用的补充。
适用的场景:
- 高性能计算,牺牲公平性换取吞吐;
- 面向 IO Bound 任务,减少 IO 等待上的闲置,这其实和高性能计算领域内的优势是一致的;
- Generator 式的流式计算;
- 消除 Callback Hell,使用同步模型降低开发成本的同时保留更灵活控制流的好处,比如同时发三个请求;这时节约地使用栈,可以充分地发挥 “轻量” 的优势;
但并不是万灵丹:
- 如果栈使用得不节制,消耗的内存量和系统线程无异,甚至内存管理还不如系统线程(系统线程可以动态地调整虚拟内存,用户线程的 Segmented Stack 方案存在严重的抖动问题,Continous Stack 方案管理不当也会抖动,为了避免抖动则成了空间换时间,而内核在这方面做了多少 heuristic 呢);
- IO Bound 任务可以通过调线程池大小在一定程度上缓解,目标是把 CPU 跑满即可,这点线程池的表现可能不完美,但在业务逻辑这个领域是及格的;
- 此外,一般的 python/ruby 任务并不是严格的 IO Bound,比如 ORM 的对象创建、模版渲染、GC 甚至解释器本身,都是 CPU 大户;单个请求扣去 redis 请求和数据库请求的时间,其它时间是否仍不少呢?
- CPU 上长时间的计算,导致用户线程的调度变差,不能更快地响应,单个请求的平均时间反而可能更长(诚然并发可能更高);然而这在 python 这类 GIL 语言来看并不算劣势,甚至比 GIL 的调度更好,至少 gevent 可以知道各 IO 任务的优先级,而 GIL 的调度是事实上的 FIFO;