Nodejs源码的阅读-事件循环的过程

Nodejs源码的阅读-事件循环的过程

 

解读基于node V0.2.0

 

之前我们对所有的watcher都介绍到了,后面就以具体的事件循环的例子来看,中途可能会遇到上面讲的watcher

 

我以一个读文件的例子来说:

var fs = require('fs');

fs.readFile('./main.js','utf8',function(err,text){console.log(text);});

我们来看看readfile是怎么被加入到事件队列,以及readfile的回调函数是怎么被执行到的。

 

首先var fs = require('fs');这里的fs模块的源码就在fs.js

fs.js中我们可以看到:

var binding = process.binding('fs');

......

fs.readFile = function (path, encoding_, callback) {

binding.stat...

binding.open...

Binding.read...    

}

其中process.binding是之前在main函数中绑定的函数,

NODE_SET_METHOD(process, "binding", binding);

它指向binding函数,这个函数是这么来获取模块的,

else if ((modp = get_builtin_module(*module_v)) != NULL) {

    exports = Object::New();

    modp->register_func(exports);

    binding_cache->Set(module, exports);

  } 

因为fs是一个内置的模块,所以get_builtin_module就能够获取到值,它获取到的内容是node_file.cc的内容,所以process.binding('fs')将返回node_file.cc的实例。

因此fs.readFile实际调用的是node_file.cc中的statopenread函数,关键当然是read函数。

static Handle<Value> Read(const Arguments& args) {

......

//read函数的最后:

 if (cb->IsFunction()) {

    ASYNC_CALL(read, cb, fd, buf, len, pos);

  } else {

    // SYNC

    //这里是同步调用的时候

  }

}

这里是异步调用,所以走的是ASYNC_CALL(read, cb, fd, buf, len, pos);这个调用。

#define ASYNC_CALL(func, callback, ...)                           \

  eio_req *req = eio_##func(__VA_ARGS__, EIO_PRI_DEFAULT, After,  \

    cb_persist(callback));                                        \

  assert(req);                                                    \

  ev_ref(EV_DEFAULT_UC);                                          \

  return Undefined();

 

把参数代入宏,之后:

eio_req *req = eio_read(fd, buf, len, pos, EIO_PRI_DEFAULT, After, cb_persist(cb));                                        

assert(req);                                                    

ev_ref(EV_DEFAULT_UC);                                          

return Undefined();

 

eio_xx系列的函数中,都会组建一个eio_req结构体,

 req->finish  = cb;    // cb这里是After

 req->data    = data; // data这里是 cb_persist(cb)

然后把这个结构体推入eio的请求队列。

接着ev_ref(EV_DEFAULT_UC);给我们的事件循环增加计数,目的是防止循环因为没有watcher而退出。这里其实并没有watcher加入循环队列,但是增加计数起到同样的作用。

 

这时用户的js执行完毕,继而执行process.loop();上一篇关于循环建立中我们知道事件循环这时开始启动。由于刚才引用计数增加了,所以循环一直不退出。

然后某一时刻,推入eio队列的请求完成,即文件读取完毕。

这时eio的完成队列从无变成有,由eio_init(node::EIOWantPoll, node::EIODonePoll);可知,node::EIOWantPoll此时被调用。

node::EIOWantPoll只有一句话ev_async_send(EV_DEFAULT_UC_ &eio_want_poll_notifier);作用是唤醒eio_want_poll_notifier这个watcher

ev_async_init(&node::eio_want_poll_notifier, node::WantPollNotifier);可知,node::WantPollNotifier将被调用,其核心代码是:

  if (eio_poll() == -1) {

//一次没取完

    ev_idle_start(EV_DEFAULT_UC_ &eio_poller);

  }

取出每一个eio完成队列中的元素并处理,如果一次eio_poll没处理完,就启动eio_poller这个watcher,这个watcher在前面一篇讲过就是用于一次没处理完所有完成队列的元素,于是开启等下次启动以便再次进行处理。

在处理eio的完成队列的元素时,最终调用:

# define EIO_FINISH(req)  ((req)->finish) && !EIO_CANCELLED (req) ? (req)->finish (req) : 0

也就是调用了(req)->finish (req),这个finish我们回头看下:

eio_xx系列的函数中,都会组建一个eio_req结构体,

 req->finish  = cb;    // cb这里是After

 req->data    = data; // data这里是 cb_persist(cb)

所以(req)->finish (req)将调用After这个函数。

Persistent<Function> *callback = cb_unwrap(req->data);

ev_unref(EV_DEFAULT_UC);

(*callback)->Call(v8::Context::GetCurrent()->Global(), argc, argv);

上面是After的几个关键代码,第一行是取出之前设置的回调函数,也就是我们js代码给readFile设置的回调函数,第二行是事件循环的计数减1,与之前计数加1相对应,因为读文件完成了,不需要它来保证循环队列非空了,第三行就是调用回调函数。

到这里所有代码都执行完成了。然后由于事件循环已经空了,所以循环也退出了,于是整个程序都退出了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值