使用 Python 进行并发编程:我为什么不喜欢 Gevent

yangdebin84 2年前
   <p>在Python的发展历史中,有过一些失败的修复CPython的缺陷和提高性能的尝试,比如消除GIL、Stackless(一个微线程扩展,避免传统线程所带来的性能与复杂度问题)、psyco (被PyPy代替)、 Unladen Swallow 。当然也有少数成功的,比如PyPy。</p>    <h3>协程</h3>    <p>在开始表达我的观点之前,我们先来了解下Coroutine。</p>    <p>Coroutine 也就是 corporate routine,直译为「协同的例程」,中文一般叫做「协程」, 实际上这个概念和进程与线程有相似之处, 因为linux线程就是所谓的「轻量级进程」。</p>    <p>我在 gevent源码分析 中找到一段表述的比较好的描述进程和协程异同的内容:</p>    <p>相同点:</p>    <p>二者都是可以看做是一种执行流, 该执行流可以挂起,并且在将来又可以在 你挂起的地方恢复执行, 这实际上都可以看做是continuation, 我们来看看当我们挂 起一个执行流时我们要保存的东西:</p>    <ol>     <li> <p>栈, 因为如果你不保存栈,那么局部变量你就无法恢复,同时函数的调用链你也无 法恢复,</p> </li>     <li> <p>寄存器的状态: 这好理解, 比如说EIP,如果你不保存,那么你恢复执行流就不知道 到底执行哪一条指令, 在比如说ESP,EBP, 如果你不保存,那么你即便有完整的栈 你也不知道怎么用. 这二者实际就是所谓的上下文,也可以说是continuation. 在执行流切换时必须保存 这两个东西, 内核调度进程时也是一回事.</p> </li>    </ol>    <p>不同点:</p>    <ol>     <li> <p>执行流的调度者不同, 进程是内核调度, 而协程是在用户态调度, 也就是说进程 的上下文是在内核态保存恢复的,而协程是在用户态保存恢复的. 很显然用户态的 代价更低</p> </li>     <li> <p>进程会被抢占,而协程不会,也就是说协程如果不主动让出CPU,那么其他的协程是不 可能得到执行机会,这实际和早期的操作系统类似,比如DOS, 它有一个yield原语, 一个进程调用yield,那么它就会让出CPU, 其他的进程也就有机会执行了, 如果一 个进程进入了死循环,那么整个系统也就挂起了,永远无法运行其他的进程了, 但 对协程而言,这不是问题</p> </li>     <li> <p>对内存的占用不同,实际上协程可以只需要4K的栈就够了, 而进程占用的内存要大 的多.</p> </li>     <li> <p>从操作系统的角度讲, 多协程的程序是单线程,单进程的</p> </li>    </ol>    <p>那用一句话描述协程的优势就是 由开发者决定协程的切换,操作系统无法干预切换,且占用内存少的多 。</p>    <p>Gevent是一种基于协程的Python网络库,它用到Greenlet提供的,封装了libevent事件循环的高层同步API。它让开发者在不改变编程习惯的同时,用同步的方式写异步I/O的代码。</p>    <p>在12-13年的时候,我也用过gevent做过一些爬虫、网络编程的工作。在我使用场景中,使用Gevent的性能确实要比用传统的线程高,甚至高很多。</p>    <p>但是发现Gevent直到现在也仍然受到国人的喜欢( 给Gevent点赞的程序员大概一半是国人 )。虽然我在《Python Web开发实战》一书中也有专门的一节介绍Gevent,但是我却不建议大家在生产环境中用它(但是我不反对协程)。</p>    <p>现在每天在Github上都会出现很多有意思的新的项目,每个项目都号称解决了XXX问题,带来了YY的思路,令人激动和振奋... 但一般人都能预测它最后会不会大火,也不知道它最后会不会无疾而终。我也曾经在这些不断新的知识和项目的冲击下迷失,渐渐的总结了一些有用的判断标准:</p>    <ol>     <li> <p>看项目社区的繁盛情况。</p> </li>     <li> <p>看Python社区和核心开发者对它的态度,比如在公开场合赞同/反对,给项目贡献代码等。</p> </li>     <li> <p>看业界有没有真的「大牛」站队,义务宣传它</p> </li>     <li> <p>有没有在复杂和高负荷的生产环境使用的案例。</p> </li>    </ol>    <p>如果你愿意花时间,你会知道Gvanrossum从来不喜欢Gevent,而是更愿意另辟蹊径的实现asyncio(基于生成器的协程)。我找几个链接:</p>    <ol>     <li> <p>Why not coroutines?</p> </li>     <li> <p>The definitive answer (according to @glyph) on why not gevent.</p> </li>     <li> <p>Async I/O for Python 3</p> </li>    </ol>    <p>Gvanrossum提到了我大部分赞同,剩下的无感也是由于理解不够深入或者还「没踩过坑」。列举下我的观点:</p>    <ol>     <li> <p>Monkey-patching。中文「猴子补丁」,常用于对测试环境做一些hack。我个人不太喜欢这种「黑魔法」,因为如果其他人不了解细节,极为容易产生困惑。Gvanrossum说用它就是"patch-and-pray",太形象了。由于Gevent直接修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。但是我们无法保证你在复杂的生产环境中有哪些地方使用这些标准库会由于打了补丁而出现奇怪的问题,那么你只能祈祷(pray)了。其次,在Python之禅中明确说过:「Explicit is better than implicit.」,猴子补丁明显的背离了这个原则。最后,Gvanrossum说Stackless之父Christian Tismer也赞同他。 我喜欢显式的「yield from」</p> </li>     <li> <p>第三方库支持。得确保项目中用到其他用到的网络库也必须使用纯Python或者明确说明支持Gevent,而且就算有这样的第三方库,我还会担心这个第三方库的代码质量和功能性。</p> </li>     <li> <p>Greenlet不支持Jython和IronPython,这样就无法把gevent设计成一个标准库了。</p> </li>    </ol>    <p>之前是没有选择,很多人选择了Gevent,而现在明确的有了更正统的、正确的选择:asyncio(下一篇会详细介绍)。所以建议大家放弃Gevent,拥抱asyncio。</p>    <p>BTW,《Python之禅》还有一句「practicality beats purity」,如果我上面说的这些问题你都有能力解决,或者知道现在以及未来不会给你造成困扰,那么用Gevent也是可以的。</p>    <p> </p>    <p> </p>    <p>来自:http://mp.weixin.qq.com/s/PbvrRZAyiLB0ZUk0VzH2IQ</p>    <p> </p>