Node.js之異步那些事

nodejs

Node.js? is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.

Node.js官網(wǎng)上的介紹铣墨,其中事件驅(qū)動非阻塞I/O模型是被大家所津津樂道的髓梅,但是有多少人真正了解其究竟呢?有人可能會想到libuv稀余,沒錯,libuv確實是其幕后英雄迟几。那么問題又來了敌厘,到底是怎么用libuv實現(xiàn)的呢?下面我們來一探究竟虐骑。

libuv

libuv當(dāng)初主要就是為Node.js開發(fā)的,提供跨平臺的事件驅(qū)動異步I/O能力赎线,當(dāng)然現(xiàn)在肯定不僅限于Node.js使用廷没。我們先來看一下libuv的Design overview

architecture

從架構(gòu)圖上看垂寥,libuv是對多個平臺上的事件驅(qū)動異步I/O庫進行了封裝颠黎,如Linux下的epoll另锋、FreeBSD下的kqueue、Solaris下的event ports狭归、Windows下的IOCP夭坪。

loop_iteration

上圖所描述的事件循環(huán)是libuv中最重要的概念,其中的Poll for I/O就是事件驅(qū)動異步I/O能力的核心过椎。到這里我們有必要先了解一些基礎(chǔ)知識室梅,Linux IO模式及 select、poll疚宇、epoll詳解亡鼠,否則后面的東西就不是特別好理解了。

正題


經(jīng)過前面的學(xué)習(xí)灰嫉,應(yīng)該對libuv有了一個整體的印象拆宛,總結(jié)一下嗓奢, libuv其實就是把各種handleio_watcher放到事件循環(huán)里讼撒,然后每一次循環(huán)都去檢查一下是否有他們關(guān)心的事件需要處理,有則調(diào)用相應(yīng)的callback股耽,沒有則繼續(xù)循環(huán)根盒。要想弄清楚Node.js之異步那些事,我們需要關(guān)心的是物蝙,Node.js如何運行事件循環(huán)炎滞,何時把handleio_watcher放入事件循環(huán),以及如何調(diào)用相應(yīng)的callback诬乞。

開始之前册赛,本次分析的代碼版本為Node.js v0.12.6,Linux平臺震嫉。

Run

node.ccStart方法運行事件循環(huán)森瘪,精華部分如下。唯一有些特別的地方就是票堵,在一個while循環(huán)中包了兩個uv_run扼睬,模式分別是UV_RUN_ONCEUV_RUN_NOWAIT,其原因在中間的兩行注釋中已經(jīng)說得很明白了悴势。

...
    bool more;
    do {
      more = uv_run(env->event_loop(), UV_RUN_ONCE);
      if (more == false) {
        EmitBeforeExit(env);

        // Emit `beforeExit` if the loop became alive either after emitting
        // event, or after running some callbacks.
        more = uv_loop_alive(env->event_loop());
        if (uv_run(env->event_loop(), UV_RUN_NOWAIT) != 0)
          more = true;
      }
    } while (more == true);
...

然后我們可以看看core.cuv_run方法的代碼窗宇,跟上面事件循環(huán)的流程圖是可以一一對應(yīng)的。

Data Structure

繼續(xù)看代碼之前特纤,有必要先了解一下重要的數(shù)據(jù)結(jié)構(gòu)和相互的關(guān)系军俊,以便更好的理解。

Data Structure

io_watcher

接著我之前文章Node.js之HelloWorld背后的大坑的思路捧存,還拿Hello World舉例子粪躬,跟libuv有關(guān)的代碼都在tcp_warp.cc里面了官硝。

  • TCPWrap::New
New

stream.cuv__stream_init方法有如下代碼,將io_watchercb設(shè)置為uv__stream_io短蜕,fd設(shè)置為-1氢架,這里只是在stream層面做的初始化設(shè)置,后面到tcp層面還會有相應(yīng)的改變朋魔。

  uv__io_init(&stream->io_watcher, uv__stream_io, -1);
  • TCPWrap::Bind
Bind

tcp.cmaybe_new_socket方法中岖研,uv__socket方法生成了新的fduv__stream_open方法將其設(shè)置到io_watcherfd警检。

  • TCPWrap::Listen
Listen

tcp.cuv_tcp_listen方法中有如下代碼孙援,將io_watchercb設(shè)置為uv__server_iouv__server_io里面會調(diào)用connection_cb扇雕,connection_cb已經(jīng)被設(shè)置為cb拓售,而這個cb正是tcp_wrap.cc中的TCPWrap::OnConnection方法。

...
  tcp->connection_cb = cb;

  /* Start listening for connections. */
  tcp->io_watcher.cb = uv__server_io;
  uv__io_start(tcp->loop, &tcp->io_watcher, UV__POLLIN);
...

core.cuv__io_start方法有如下代碼镶奉,利用void* watcher_queue[2]變量將io_watcher加入到uv_loop_t的隊列中去础淤,具體操作詳見queue.h。將uv_loop_tuv__io_t** watchers當(dāng)做數(shù)組使用哨苛,fd為下標(biāo)鸽凶,io_watcher為對應(yīng)的值。

...

  if (QUEUE_EMPTY(&w->watcher_queue))
    QUEUE_INSERT_TAIL(&loop->watcher_queue, &w->watcher_queue);

  if (loop->watchers[w->fd] == NULL) {
    loop->watchers[w->fd] = w;
    loop->nfds++;
  }
...

uv__io_poll

linux-core.c中的uv__io_poll方法建峭,一行一行的讀就可以了玻侥,前面的鋪墊已經(jīng)做得很充分了,只要讀懂謎底便可揭曉亿蒸。

未完


  • 接下來我們來說說process.nextTick(callback)的事凑兰,在node.js中定義如下,把callback放到了nextTickQueue隊列中边锁,那么Node.js是在什么時候消費這個隊列的呢姑食?
    function nextTick(callback) {
      // on the way out, don't bother. it won't get fired anyway.
      if (process._exiting)
        return;

      var obj = {
        callback: callback,
        domain: process.domain || null
      };

      nextTickQueue.push(obj);
      tickInfo[kLength]++;
    }
  • tcp_wrap.ccTCPWrap::OnConnection方法有如下代碼,MakeCallback方法的出處如下圖砚蓬。
  tcp_wrap->MakeCallback(env->onconnection_string(), ARRAY_SIZE(argv), argv);
MakeCallback
  • async-wrap.ccMakeCallback方法有如下代碼矢门。
  env()->tick_callback_function()->Call(process, 0, NULL);
  • node.ccSetupNextTick方法有如下代碼,對tick_callback_function()進行了設(shè)定灰蛙。
  env->set_tick_callback_function(args[1].As<Function>());
  • node.ccSetupProcessObject方法有如下代碼祟剔,SetupNextTick被設(shè)定為process中的_setupNextTick方法。
  NODE_SET_METHOD(process, "_setupNextTick", SetupNextTick);
  • node.jsstartup.processNextTick方法有如下代碼摩梧。
  process._setupNextTick(tickInfo, _tickCallback, _runMicrotasks);
  • node.js_tickCallback方法代碼如下物延,消費nextTickQueue隊列中的callback方法。
    function _tickCallback() {
      var callback, threw, tock;

      scheduleMicrotasks();

      while (tickInfo[kIndex] < tickInfo[kLength]) {
        tock = nextTickQueue[tickInfo[kIndex]++];
        callback = tock.callback;
        threw = true;
        try {
          callback();
          threw = false;
        } finally {
          if (threw)
            tickDone();
        }
        if (1e4 < tickInfo[kIndex])
          tickDone();
      }

      tickDone();
    }

省略去中間步驟仅父,實際上是產(chǎn)生了如下的調(diào)用關(guān)系叛薯。

TCPWrap::OnConnection()
↓↓↓
_tickCallback()

總結(jié)


簡單說浑吟,整個過程是這樣的,事件循環(huán)中有相應(yīng)I/O事件發(fā)生的時候耗溜,libuv調(diào)用Node.js C++部分的回調(diào)组力,C++部分調(diào)用JavaScript部分的回調(diào),順便調(diào)用nextTick設(shè)定的回調(diào)抖拴。

還是認(rèn)真讀代碼吧燎字,以上寫的僅供參考。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阿宅,一起剝皮案震驚了整個濱河市候衍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洒放,老刑警劉巖蛉鹿,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異往湿,居然都是意外死亡妖异,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門煌茴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來随闺,“玉大人,你說我怎么就攤上這事蔓腐。” “怎么了龄句?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵回论,是天一觀的道長。 經(jīng)常有香客問我分歇,道長傀蓉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任职抡,我火速辦了婚禮葬燎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缚甩。我一直安慰自己谱净,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布擅威。 她就那樣靜靜地躺著壕探,像睡著了一般。 火紅的嫁衣襯著肌膚如雪郊丛。 梳的紋絲不亂的頭發(fā)上李请,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天瞧筛,我揣著相機與錄音,去河邊找鬼导盅。 笑死较幌,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的白翻。 我是一名探鬼主播绅络,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嘁字!你這毒婦竟也來了恩急?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤纪蜒,失蹤者是張志新(化名)和其女友劉穎衷恭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纯续,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡随珠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了猬错。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窗看。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖倦炒,靈堂內(nèi)的尸體忽然破棺而出显沈,到底是詐尸還是另有隱情,我是刑警寧澤逢唤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布拉讯,位于F島的核電站,受9級特大地震影響鳖藕,放射性物質(zhì)發(fā)生泄漏魔慷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一著恩、第九天 我趴在偏房一處隱蔽的房頂上張望院尔。 院中可真熱鬧,春花似錦喉誊、人聲如沸邀摆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隧熙。三九已至,卻和暖如春幻林,著一層夾襖步出監(jiān)牢的瞬間贞盯,已是汗流浹背音念。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留躏敢,地道東北人闷愤。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像件余,于是被迫代替她去往敵國和親讥脐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容