对Python协程的一点理解

很早以前了解到协程(或者纤程),只有个硬生生的概念,进程可以分成多个线程,而线程又可以分为多个协程,不仅上下文切换的代价更小,而且协程运行在用户态可以避免不必要的系统调用。

最近对协程的了解加深了一些。首先要知道,如果协程跑在单一线程里,系统地并行性能不会提高,并不会利用多核的威力,但是对并发的支持却可以大大提升。因为现实中的计算机,从寄存器到缓存,再到内存和磁盘以及网络,延时是越来越大的,因此在处理IO密集型的任务,瓶颈不在CPU而在IO,由事件来驱动就是很自然的事情,事件循环是异步编程的基础。在等待IO完成的过程中,CPU会去处理其他的请求,一旦接到某IO就绪的通知,再调用回调函数继续处理剩下的事情。哪怕要提升CPU的利用率,还可以采用多进程+事件的方式(例如大名鼎鼎的Nginx),比起多线程要更加高效,毕竟多线程需要消耗大量系统资源,管理线程也是件麻烦事(即便有线程池)。

Linux上的Epoll和FreeBSD的Kqueue是最常见的非阻塞IO模型,个人简单的理解,就是原来程序员需要写代码去轮询IO是否完成,现在由系统接管了,而且这套处理被放入了内核中,不仅运行更高效,而且进行了一定的优化,比如数据在底层的拷贝,可以通过内存映射等手段提升效率。原来大量的文件描述符都托管给了系统,由系统来决定何时调用注册过的回调函数。

为了方便程序员,通常会有Library封装底层的函数调用,提供更好的接口(甚至可以处理一些跨平台的工作),比如Libevent / Libev / Libuv就是这类事件库,可以屏蔽掉Epoll的一些细节。Nodejs就是基于这类Library的一个Javascript实现。但不管怎样,异步事件模型的编程始终不如写同步程序来得亲切,代码中容易出现callback hell,这个时候协程就派上用场了。

协程和函数一样,都是一种子程序,只不过函数调用是基于栈的,而协程之间是可以切换的,就好比一段子程序执行到一半,当遇到IO调用需要等待时,可以自动切换到另一段子程序,IO完成后再切回来继续执行。这样有了协程,我们的程序写起来看上去就更像同步程序了,从系统的角度来看,程序运行起来依然是基于事件驱动的,在状态就绪以后执行回调函数,只不过脏活累活都交给编译器/解释器和程序库去解决了,程序员的代码看上去就简洁清晰了。

很多年前Erlang就在语言层面支持了协程,上个世纪互联网行业还处于初级阶段,但电信行业已经运用Erlang/OTP来处理高并发,只可惜当年开源并未如火如荼,爱立信的推广也不够,Erlang一直未成为主流。Erlang的编程模型基于Actor模型,历史是相当悠久了。然后Google搞出了个Golang,也从语言层面支持协程,只不过采用的是同样历史悠久的CSP(通信顺序进程)模型,以这种方法支持协程的,还有JVM上的Clojure语言。虽然对Golang的争议很多,但是用它写并发很舒服却是公认的,难怪在云计算领域Go语言取得了不错的成绩。

以上说的都是我对协程的理解,和Python并没有沾上边。实际上,相对于Erlang和Go,Python对协程的支持并不算很成熟,直到今天官方推行的AsyncIO也未能取得一统天下的优势。前面讲到协程作为子程序是可以彼此切换运行的,当然也需要保存一些上下文的状态,Python中有生成器的概念,使得程序执行在遇到yield时会暂停,如果给生成器send一个值,程序又在原来暂停的地方继续向下执行。Python实现协程的思路也借助了生成器的概念,在Py3.4中,可以用asyncio.coroutine将生成器标记为协程,然后将它们丢尽一个事件循环中,而生成器协程中的yield from将一个Future对象传递给事件循环,此时Future对象并未就绪,外部的事件循环会选择另一个协程继续运行,一旦Future对象完成,事件循环检测到状态变化,就会将Future对象的结果通过send发送给协程从而恢复工作。

随着升级到3.5版本,Python引入了async和await关键字来替代asyncio.coroutine和yield from,原理上并没有改变,只是yield from等待的是一个生成器对象,而await等待的是一个实现了__await__方法的awaitable对象,在python中协程也是awaitable的。然而到了3.6版本似乎一些细节上又做了改动。这样看来,asyncio和它的事件循环才是最重要的,async和await更像是一种API,只不过是以语言的关键字的形式出现,若没有asyncio开启的事件循环和相应的处理,光靠两个关键字是没啥作用的。如果嫌弃每次用asyncio都要启动一个循环很麻烦,目前也有其他第三方程序库可用,也能支持async/await语法,但本质上仍然需要程序库先开启一个循环。这么看来,相比Erlang和Golang在语言层面的原生支持,Python对协程的支持度还是略逊色一点。

另外,目前asyncio这一套机制和已有的网络库是不兼容的,因此陆陆续续出现很多aio打头的程序库,可以看作是一套新的生态。AsyncIO还有其他许多让人难以理解的地方。早就有人提出过为了支持协程可以在python中加入一套新的机制,而python选择在生成器的基础上去扩展,慢慢的也体现出一些弊端。可以参考Flask框架的作者Armin Ronacher写的一篇文章《I Don't Understand AsyncIO》,指出了一些问题,当然,我也并没有完全看懂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值