为何node.js需要coroutine【转】

转自 http://shiningray.cn/node-js-coroutine.html

这篇文字以前看到时候印象不错,但是没有体会,最近重新阅读,觉得非常棒,转一下。

=================

这其实就是我关心为何node.js需要coroutine。

很多语言,像Java C/C++,虽然要深入了解他们,很复杂,但一个应用可以由资深的程序员,写出一个框架,然后通过框架隐藏那些复杂的细节,然后由其他初级程序员来编程实际的复杂应用逻辑。现在很多外包公司就是这样,甚至是由主程序员写一个代码模板,然后让其他小弟们来改改。

Python、Ruby就在这上面更进一步,不仅可以开发一个框架,还能设计一种DSL,让逻辑的编写更加简单。

但是node.js虽然隐藏了event-loop和async io的细节,但是却把异步处理的流程控制的问题丢给了开发人员。即使是上层负责逻辑的程序员,也常常被异步所干扰。

计算机本身其实多个不同的设备之间进行通信,必须要考虑很多同步的问题。冯诺依曼机的理念就是把一些同步发生的东西,通过时钟进行同步,让本来必须考虑并行的编程,简化为了串行编程,然后我们就可以使用简单的流程图了。这是冯诺依曼机最大的贡献。

很多时候,对于编程中的某个连续的逻辑来说,我其实并不关心读取文件是阻塞还是异步,请求数据库要考虑超时什么的。我的目的其实很简单,就是读取文件,获得内容,然后放入数据库等等。

而现在node.js的做法似乎就是抛弃了这种模式,但是node.js却无法实现真正的并行编程,比如利用多核,这是很奇怪的事情。


然而如果有了coroutine,那么我们可以设计出一个框架,在框架之上,普通程序员还可以继续按照以前的方式来写代码,像底层的异步操作则应该是资深程序员关心的事情。

比如,一个从a文件读取内容,然后写入b文件的一个代码:

这个是node.js的代码

fs.readFile('a.txt', function(err, data) {
  if(err){throw err;}
  fs.writeFile('b.txt', data, function(err){
    if(err){throw err;}
  });
});

而传统方式的伪代码如下:

try{
var data = fs.readFile('a.txt');
fs.writeFile('b.txt', data);
}catch(err){}

很明显是传统方式的伪代码更加清晰,node.js使用的CPS给人感觉非常冗长。如果有coroutine,那么node.js还可以进一步把核心库中的*Sync版本给删减。

那么有了Coroutine的话,我们可以给原先的代码改成这样

function readFileSync(file){
   var co=Coroutine.current();
   fs.readFile(file, function(err, data){
     co.resume(data); //1
   });
   return Coroutine.yield(); //2
}
var data = readFileSync('a.txt')

这段代码使用当前的coroutine来等待io操作,会在(2)处挂起当前coroutine,等待唤醒,当异步i/o执行完成以后,则在回调函数的(1)处唤醒当前的协程。

当然这样写的话会阻塞当前的协程,如果要不阻塞当前的协程,我们可以这样写:

var co = Coroutine.create(function(){
   var data=readFileSync('a.txt');
   writeFileSync('b.txt', data);
});
co.resume();

这样创建一个新的协程把任务单独隔离开来,至于你什么时候需要调用该协程,则由自己安排。

这样使用Coroutine的好处是,你可以对自己的每一个请求进行概念抽象,把每个请求封装成coroutine,那么在这种请求中的处理逻辑还是可以按照原来的方式去编写。

有些人可能认为coroutine太浪费内存,但据目前很多coroutine在高并发程序中的应用来看,是可以接受的;而回调模型要引入大量的函数对象以及大量闭包,未必就能更省内存。

另外还有人提到Coroutine也可能会出现一些同步问题,但这并不能成为不使用Coroutine的理由,写的不好的回调机制一样也会产生同步问题。


实际的应用可能更加复杂,举个例子。在糗事百科的web端,常常要先从memcached中获取缓存,如果缓存不存在,则读取数据库,然后再把内容存入memcached中。这是个很常用的逻辑。来段同步版伪代码

var data = Memcache.get('key');
if(!data){
  data = Mysql.execute(sql);
  Memcache.set('key', data);
}
//后面的代码

当然这里也不考虑什么竞争条件什么的问题了。
如果是node.js,那么则变成了

function rest(data){
  //后面的代码
}
Memcache.get('key', function(err, data){
   if(data){
     rest(data);
   }else{
     Mysql.execute(sql, function(err, data){
        Memcache.set('key', data);
        rest(data);
     });
   }
});

本来很清晰的代码,却被回调函数弄得支离破碎,即使使用一些像Step/Do这些DSL来协助,还是不如原来的更加直观。


综上所述,node.js的特点是易于上手,容易编写,同时性能还不差,但问题在于,到了一定复杂程度之后,代码编写就比较困难了,不容易构建较复杂的应用,所以引入Coroutine才可以让node.js真正进入大型应用的领域。

PS,其实有call/cc也可以解决很多问题。

Python使用微软Access

PyODBC是一个很好的软件,可以用来连接使用微软Access数据库,http://code.google.com/p/pyodbc/

快速示例在这里 http://code.google.com/p/pyodbc/wiki/GettingStarted

也可以使用PyWin32COM来连接ODBC。

可以用这个模块来方便访问 http://pypi.python.org/pypi/execsql/0.4.4.0

这个工作主要用在一个数据库迁移的小项目上。

Sqlite与Access互相转换的一个软件,不知是否可用

http://sqlite.awardspace.info/syntax/sqliteodbc.htm

在Sqlite中建立Access数据库

http://code.activestate.com/recipes/572165-recreate-ms-access-table-in-sqlite/

协程Coroutine相关资料汇集 – Python/Lua

http://www.inf.puc-rio.br/~roberto/docs/corosblp.pdf
Coroutines in Lua
Ana L´ucia de Moura , Noemi Rodriguez , Roberto Ierusalimschy

http://www.inf.puc-rio.br/~roberto/docs/MCC15-04.pdf
Revisiting Coroutines
Ana L´ucia de Moura and Roberto Ierusalimschy

http://ravenw.com/blog/2011/08/24/coroutine-part-1-defination-and-classification-of-coroutine/
协程(一)协程的定义与分类
协程的概念最早由Melvin Conway在1963年提出并实现,用于简化COBOL编译器的词法和句法分析器间的协作,当时他对协程的描述是“行为与主程序相似的子例程”。

http://ravenw.com/blog/2011/09/01/coroutine-part-2-the-use-of-coroutines/
协程(二):协程的应用
Conway提出协程这个概念时所解决的编译器问题就属于生产者-消费者问题。
生成器实际上可以看作只有生产者的生产者-消费者模型。
目标导向编程是指模式匹配(Pattern-matching)或Prolog的查询这样的系统,由用户提出一个形式化定义的目标(Goal),系统会在一系列可选的子目标中寻找直到确认一个解决方案。在寻找过程中常常会需要回溯(Backtracking)机制,这种机制使用完全非对称协程作为生成器很容易实现。
在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其它协程共享全局数据和其它资源。目前主流语言基本上都选择了多线程作为并发设施,与线程相关的概念是抢占式多任务(Preemptive multitasking),而与协程相关的是协作式多任务。

http://ravenw.com/blog/2011/09/06/coroutine-part-3-coroutine-and-continuation/
协程(三)协程与Continuation
Continuation是一种描述程序的控制状态的抽象,它用一个数据结构来表示一个执行到指定位置的计算过程;这个数据结构可以由程序语言访问而不是隐藏在运行时环境中。Continuation在生成之后可作为控制结构使用,在调用时会从它所表示的控制点处恢复执行。

http://simple-is-better.com/news/426
Gevent 任务的持续追加和执行

http://simple-is-better.com/news/363

http://blog.jqian.net/coroutine.html

http://gashero.iteye.com/blog/442177
协程才是未来-性能夸张的协程服务器,基于eventlet(greenlet)的http性能测试

http://blog.csdn.net/lanphaday/article/details/5397038
协程三篇之一(协程初接触)

http://codeprac.iteye.com/blog/1060778
coroutine资源索引
c语言中实现coroutine有以下几种方法

http://en.wikipedia.org/wiki/Generator_%28computer_programming%29
Python相关概念

http://fuliang.iteye.com/blog/857644
Ruby Coroutine
Ruby1.9提供了Fiber,提供了Coroutine的功能。
Fibers作为实现轻量级合作并发的基础设施,和线程很像,提供了创建一个可以pause和resume的代码块,但Fibers是非抢占式的,必须由程序而不是VM来调度。每一个fiber提供了4kB的栈空间可以允许fiber的block进行深层嵌套函数调用。这是rdoc对Fiber的描述。

http://www.dabeaz.com/coroutines/index.html
A Curious Course on Coroutines and Concurrency
http://www.dabeaz.com/coroutines/Coroutines.pdf

http://www.python.org/dev/peps/pep-0342/
Coroutines via Enhanced Generators

http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators
How do Python generators differ from Lua coroutines?
http://sunxiunan.com/?p=1654
【译文】比较Lua协程与Python生成器
Lua最重要的特性,coroutine.yield()是个一般函数,可以在coroutine.resume()动态扩展的任意位置被调用,限制是你不能yield操作C回调函数(除非你使用了Coco库)。在Python中,yield是一个语法,只能在生成器(generator)的语句体里存在。

Some Python Projects:
http://www.gevent.org/
http://codespeak.net/py/0.9.2/greenlet.html
https://github.com/slideinc/gogreen
https://github.com/j2labs/brubeck
https://github.com/sampsyo/bluelet

—————–
Lua
—————–

http://lua-users.org/wiki/CoroutinesTutorial

Lua5.1 Coroutine
http://www.lua.org/manual/5.1/manual.html#2.11

Lua5.2 Coroutine
http://www.lua.org/work/doc/manual.html#2.6
http://www.lua.org/work/doc/manual.html#6.2

http://lua-users.org/wiki/MultiTasking

http://kotisivu.dnainternet.net/askok/bin/lanes/index.html
Lua Lanes

http://keplerproject.github.com/copas/manual.html#introduction
如果想实战使用的可以试试copas

http://sunxiunan.com/?p=1689
使用Lua协程实现斐波拉切

http://mryufeng.iteye.com/blog/211650
luacoco 增强lua的coroutine功能

http://mryufeng.iteye.com/blog/211004
lua coroutine是如何实现的?
lua的coroutine是通过c的堆栈来保存调用上下文的 多少个coroutine嵌套 就有多少个luaV_execute, 而lua的state保存存在lua_State,也就是thread对象中。
顺着调用链 再看下源代码很容易就明白了。

http://sunxiunan.com/?p=1793
Re [CPyUG] Coroutine协程和闭包本质上有什么区别么
就我个人浅见,闭包概念比较容易理解使用,而协程是一个动态交互的动作,涉及到主程序和协程子程序之间的交换,理解起来要麻烦多多。

http://timyang.net/lua/lua-coroutine/
TimYang
协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈,局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。线程与协同程序的主要区别在于,一个具有多线程的程序可以同时运行几个线程,而协同程序却需要彼此协作地运行。就是说,一个具有多个协同程序的程序在任何时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显示地挂起时,它的执行才会暂停。

http://blog.codingnow.com/2011/08/lua_52_multithreaded.html
coroutine 可以实现一个协同多线程模型。即,每个线程(coroutine) 只在用户期望的地方跳出来,并可以在以后跳回去(保持当初跳离的状态)。这解决了许多抢占式多线程的麻烦。lua 的发明人在一篇访谈中谈到了 coroutine 解决并发的问题。
lua vm 本身的状态可以保留在 L 中,但 C 函数的状态却丢失了,无法正确返回。Lua 5.2 为了解决这个问题,引入了新的 api ,有兴趣可以阅读新的文档的 4.7 – Handling Yields in C
lua 的早期版本也可以通过 lua coco 实现无限制的 yield 操作。但 coco 使用了 OS 的 fiber 库 这比 5.2 版的 lua 实现多出了额外的堆栈开销。

http://blog.codingnow.com/2010/12/lua_cothread.html
基于 lua 的 coroutine ,只是写了个简单的调度器。

http://blog.codingnow.com/2010/06/masterminds_of_programming_7_lua.html
打算如何处理并发问题?
使用协程(coroutine),我们可以共享内存,但不是抢占式的。不过这个技术利用不到多核机器。但在这类机器上,使用多“进程”就能极大的发挥其性能。这个我提到的“进程”是指在 C 里的一个线程,这个线程维护自己独立的 Lua 状态机。这样,在 Lua 层面上,就没有内存共享使用。在《Lua 程序设计第二版》[Lua.org] 中,我给出了这种方式的一个原型。最近我们已经看到有些库支持了这种方式(比如 Lua Lanes 以及 luaproc)。

没有支持并发,但你为多任务实现了一个有趣的解决方案:非对称式协程。它们如何工作的?
Luiz:在我们的 HOPL 论文中,对那些设计决策全部做了极为详细的解释说明。
Roberto:我们最终选择了非对称式模型。它的基本思想非常简单。通过显式调用 coroutine.create 函数来创建一个协程,把一个函数作为协程主体来执行。当我们启动 (resume) 协程时,它开始运行函数体并且直到结束或者让出控制权 (yield) ;一个协程只有通过显式调用 yield 函数才会中断。以后,我们可以 resume 它,它将会从它停止的地方继续执行。
它的基本思想非常类似于 Python 的生成器,但有一个关键区别:Lua协程可以在嵌套调用中 yield,而在 Python 中,生成器只能从它的主函数中 yield。在实现上,这意味着每个协程像线程一样必须有独立堆栈。和“平坦”的生成器相比,“带堆栈”的协程发挥了不可思议的强大威力。例如,我们可以在它们的基础上实现一次性延续点 (one-shot continuations)。

http://blog.codingnow.com/2011/12/dev_note_4.html
这套东西的框架其实是一个 coroutine 的调度器。每个执行流(就是 case message),不论是不是并行的,都是一个 coroutine 。当遇到 listen ,fork ,break 的时候 coroutine yield 出来,由调度器来决定下一步该 resume 哪个分支就好了。
框架只需要接收外界传入的带类型信息的 message ,在调度器里维护一张消息类型到执行流的映射表,就可以正确的调度这些东西。

https://github.com/lefcha/concurrentlua

http://www.lua.inf.puc-rio.br/luagravity/
The link and await primitives are the supported reactivity mechanisms of LuaGravity. 类似C#的await

http://coco.luajit.org/
Lua的协程功能不支持C函数扩展,那么可以试试大名鼎鼎的LuaJit作者出品的Coco

关于Green Thread(绿色环保线程)、Native Thread,以及线程的一些普及问题,下面这个presentation很不错(关于Ruby)
http://www.slideshare.net/tmm1/threaded-awesome-1922719

维基百科上的这一条
http://en.wikipedia.org/wiki/Thread_%28computer_science%29

http://c2.com/cgi/wiki?SameFringeProblem
here’s the killer example of the utility of CoRoutines. The problem setup is quoted from RichardGabriel’s 1991 paper “The Design of Parallel Programming Languages” (http://www.dreamsongs.com/10ideas.html)

http://code.google.com/p/js-coroutine/
Lua style coroutine support for V8 JavaScript