More Related Content
Similar to Ruby vs Node ShiningRay Shanghai (20)
More from Jackson Tian (12)
Ruby vs Node ShiningRay Shanghai
- 1. Ruby vs Node EventMachine & Fiber ShiningRay 曹力 2011.6.18
- 26. 不同语言的并发机制比较 任务单元 信息传递交互机制 多核支持 Node.js Callback Callback Multi-node Cluster Ruby Fiber(stdlib) ... JRuby Erlang process message 内置 自动 Go goroutine channel 内置 自动 Scala actor message 内置 自动 Python Coroutine(stdlib) Greenlets(3rdparty) ... Multi-processing(stdlib) Stackless Python Microthreads Channel Lua Coroutine
Editor's Notes
- 过去使用一个进程或线程处理一个请求,浪费大量的内存以及进行大量的上下文切换而限制了并发量,多线程模型还容易因为锁而导致各种性能问题。所以出现了 Reactor 模式的本质就是使用一个事件循环来检测事件,并调用相应的处理器。 Reactor 通常是单线程单进程运行的,使得它在处理大量请求时可以节省很多内存,并且没有大量的上下文切换反而效率更高。并且现在的操作系统通过将事件通知机制集成到内核中,可以达到更高的性能和并发量
- 由于 Web 应用大行其道,为了解决高并发的问题,在这个模式的思路下,出现了很多框架,比如我们这个聚会的主题 Node.js ,就是其中非常耀眼的新星。
- 那为啥我今天要讲 Ruby 呢,其实就是因为 nodejs 我不会。 node.js 09 年才创建,而我 08 年就开始考虑开发实时的 Web 游戏,当时主要使用 Ruby on rails 开发网站,所以很自然的就使用了 Ruby 的 EventMachine 。
- EventMachine 是基于 libevent 开发的, node.js 则是基于 libev 开发的, libevent 和 libev 都是为了简化简化事件机制在不同的平台上开发的难度。 Ruby 中基于 libev 开发的有 Rev 和 Cool.ioRuby 中基于 libev 开发的有 Rev 和 Cool.io ,但都没有 eventmachine 成熟和稳定
- EventMachine 必须要明确使用这里的代码来启动事件循环
- 这是一个很简单的 echo server ,这里可以看到, EventMachine 使用的是回调模块来处理事件,而非 Nodejs 的事件机制
- 对比一下 nodejs 中的 echo server ,复杂度上很接近
- 这段代码是用于远程读取网站内容,我们可以使用 EventMachine 包装好的 HttpRequest ,让他看上去更像是 nodejs 的风格
- 注意这里的 http 对象,是一个 Deferrable ,也就是 Future/Promise 模式,我看到 nodejs 中也有类似的库用于 可以给 Deferrable 对象设置一个成功的回调函数 也可以设置一个出错的回调函数 还可以设置 Deferrable 的超时
- 这种回调函数的写法,如果只嵌套一两层,还是可以控制的,但如果遇到业务逻辑非常复杂的应用,全使用回调函数就会非常混乱
- 这种使用回调函数的编码方式,在学术上是称之为 Contiuation-Passing Style ,延续传递风格。 Continuation 是一种非常古老的程序结构,关于它的理论分析可谓渊源流长, continuation 简单的说起来就是 entire default future of a computation, 即对程序 " 接下来要做的事情 " 所进行的一种建模 . 所谓 CPS 风格,不 " 返回 " 值 , 而是把值作为一个参数传递给 continuation 从而 " 继续 " 处理值 . 延续有时候会被描述为“带参数的 goto” 。 很多论文对 CPS 有很多描述,可以通过一些转换,把 CPS 风格转换成
- coroutine 是一种控制结构,常用于实现迭代器,无限列表,管道,以及协作多任务,也就是相对于线程和进程的抢占式多任务,必须手工进行调度,也常用于在顺序编程中实现并发 Coroutine 可以由 Continuation 来实现,从理论上 Coroutine 就是两个或者更多的 Continuation 来互相调用。在 Lisp 和 Scheme 之类的语言中,可以使用 call/cc ( call with current continuation )这个很难用很晦涩的指令来实现 Coroutine 。刚好 Ruby 也支持 call/cc 。当然由于机制问题,主流编程语言中实现 Coroutine 不是用 callcc 像 generator 也是一种 coroutine 的例子, JavaScript 1.7 中就引入了 yield 关键词来简化 generator 的开发。如果语言支持 Generator ,也可以通过 Generator 实现 Coroutine ,目前好像只有 Firefox 的 Spidermonkey 支持 yield , V8 貌似不支持。
- Ruby 1.9 中引入了 Fiber , Fiber 是 Coroutine 的另一种叫法,只不过 Coroutine 强调的是它是一种控制结构,一种语言结构, Fiber 强调的是它的系统结构和与线程 Thread 相呼应 Fiber 通过 yield 和 resume 来对自身进行控制, yield 用于将控制权还给父级过程, resume 用于恢复指定 Fiber 的运行。 通过运用 Fiber ,我们可以把原本异步处理的内容,看上去好像是同步操作的一样。
- 我们来看一段使用 CPS 风格的代码,这段代码要先检查 google.co 是否存在,如果存在再去抓取其中一个子页面。 如何把这段代码改成不使用 CPS 的顺序编码的风格呢?
- 在这里我们增加一个 Fiber ,首先将任务单元包裹在一个 Fiber 中,其次,我们在异步操作之前,记住当前这个 Fiber ,然后进行异步请求,然后暂停当前的 Fiber ,把控制权交还给父过程。当异步操作完成时,会调用事前设定的回调函数,这时我们将之前记住的 Fiber 唤醒,并把异步调用的结果传给他,通过这个方式,任务单元内的过程,就可以像顺序代码一样写了。
- 在这种思路的影响之下, Ruby 社区就有人写了一个叫做 Em-synchrony 的库,用来帮助简化 Reactor 模式中的异步调用。
- 要使用 em-syncrhony ,首先就要把之前的启动事件循环的 EM::run 改成 synchrony 方法,然后它会把大部分 EM 自带的异步操作 API ,换成同步模式
- Em-synchrony 也可以灵活使用异步、同步组合的方式来编程。 大家知道,如果是同时进行多个异步操作,而且之间没有什么关联的话,我们并不用等待一个结束再去启动另一个,而是可以一下将这些全部发起。 EM-Synchrony 中有个 Multi 类,可以帮助达到这种效果,大家看这段代码, Multi 类把几个异步操作放到了一起,当所有操作都结束后,可以取得所需要的内容。这里的“ aget” 是原本的异步模式的 get Node.js 中也有很多库是实现了这些异步操作模式,比如 async 然而,实际上这些库都是多出的额外的复杂度,其实大部分情况下我们只需要顺序编程就能搞定业务逻辑。
- 大家都知道第一台电子计算机是 ENAIC ,但是大家是否知道, ENIAC 其实是台并行计算机,其实同期的很多大型机,都是并行的,科学家们要精确考虑不同元器件之间的信号同步。 然而今天的个人计算机,都是基于冯诺依曼结构的,冯诺依曼通过简化了计算机的模型,将并行转为串行,提升了计算机的弹性,也降低了编程的复杂度。他使用了时钟同步器来同步不同元器件之间的信号。
- 为何串行的冯诺依曼结构能够发扬广大呢?除了冯诺依曼结构能够简化计算机结构,让计算机变得更易于编程和更有弹性之外。我认为他最大的优势在于符合人类的思维模式。 我们处理的大部分“业务”,都可以按部就班一步步来做。特别对于新学 当然,对于人而言,能一次做好一件事情就够了,但是我们希望计算机能替代人类来同时处理大量任务
- 大家看这个编程语言排行榜,大部分流行的语言,都是比较偏顺序编程的,我们的 JavaScript 只能排到 11 名,大家尤其可以看到, PHP ,你不能否认 PHP 在 Web 开发领域中的霸主地位,但谁会关心 PHP 语言本身能不能高并发呢。
- 所以首先我们考虑一下我们选择 Node 的首要原因是什么?是因为它性能很好么?如果真正需要并发性,我们可以考虑使用 Erlang 或 Scala ,它们无论在并发性还是运算性能上都优于 V8 ,而且在语言层面上就可以直接利用多核的优势。如果还需要进一步提升性能,甚至可以考虑使用 Java 甚至 C++ 。 V8 的性能只是相对于其他一些脚本语言有一定的优势。 所以,我们选择 Node 的首要原因应该是,它的简单,我们可以使用很少量的代码就能实现一个性能还不错的高并发的服务器。而且 JavaScript 在浏览器中的广泛应用让我们不需要再另外学习一种新的语言,所以我们才选择了 Node 。
- 从这个表格上看到,几大在语言层面就直接集成了并发机制的语言,如 Erlang Scala Go Stackless ,都是将顺序代码封装成任务单元。 可见, Coroutine 是主流,虽然 Coroutine 不可避免也存在占用更多内存,更多上下文切换,但是得到开发效率的提升,是值得的
- 此外我们可以考虑 28 原则,对代码进行划分
- 20% 代码可能占用 80% 的运行时间,这些问题可能根本不在于 COroutine 上 20% 的代码确实需要异步来提高并发性,而实际上另外 80% 根本用不着非得异步 在团队里可能有 20% 的人是牛人,让他们搞定异步方面的 API ,然后让剩下 80% 的人手去用同步的方式开发,效率更好
- 所以,我强烈建议 nodejs 要引入 coroutine 来解决目前编程中的麻烦。
- 最后给一个实验性的 node fiber 的实现,不过编译之后调用的是 node-fiber ,而不是一般的 node