淺析libuv源碼-node事件輪詢解析(1)

  很久沒寫東西了,忙得要死,前幾天忽然想起了脈脈上面一句話: 要時刻保持本身的競爭力。因此,繼續開寫!
  通常的JavaScript源碼看的已經沒啥意思了,我也不會寫什麼xx入門新手教程,最終決定仍是啃原來的硬骨頭,從外層libuv => node => v8一步步實現原有的目標吧。
  libuv核心仍是事件輪詢,前幾天從頭至尾看了一遍官網的文檔,對此有了一些更深的理解。
  (雖然如今開發用的mac,可是爲了銜接前面的文章,因此代碼仍舊以windows系統爲基礎,反正差異也不大)
  首先看一眼官網給的圖:


  理論上輪詢都是一個無盡循環,因此不用在乎loop alive問題。
  上圖中,關於udpate loop time、Run due timers兩塊內容我已經在別的博客中講解過,這裏就懶得發傳送門了。
  有兩個簡單的概念須要稍微提一下,libuv中有兩個抽象概念貫穿整個框架:handle、request。其中handle生命週期較長,且有本身回調方法的一個事務,好比說TCP的handle會處理每個TCP鏈接,並觸發connection事件。request屬於handle中一個生命週期短,且簡單的行爲,好比向文件進行讀、寫等等。
  這一篇主要看一下接下來剩餘的部分,因爲性質不太同樣,因此並不會按順序依次分析,而是從易到難,且源碼會作大量簡化,有興趣的人能夠本身去看原生態的。
  事件輪詢方法源碼精煉以下:
int uv_run(uv_loop_t *loop, uv_run_mode mode) {
  // ...

  while (r != 0 && loop->stop_flag == 0) {
    // update loop time
    uv_update_time(loop);
    // run due timers
    uv__run_timers(loop);
    // call pending callbacks
    ran_pending = uv_process_reqs(loop);
    // run idle handles
    uv_idle_invoke(loop);
    // run prepare handles
    uv_prepare_invoke(loop);
    // poll的阻塞時間處理
    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);
    // poll for I/O
    if (pGetQueuedCompletionStatusEx)
      uv__poll(loop, timeout);
    else
      uv__poll_wine(loop, timeout);

    // run check handles
    uv_check_invoke(loop);
    // call close callbacks
    uv_process_endgames(loop);
  }
  // ...
  return r;
}複製代碼

Call close callbackshtml

  這類回調比較特殊,官網是這麼解釋的: Close callbacks are called. If a handle was closed by calling uv_close() it will get the close callback called.
  簡單來說,就是僅在爲了關閉一個handle,調用uv_close方法中所帶的callback會被認爲是一個close callbacks。在使用node的時候,全部的操做(好比fs.readFile)不可主動取消,因此輪詢中這一步在JS層面是感知不到的。
  做用上至關於vue鉤子函數中的destroy,因爲觸發是在輪詢的最後一步,適合作一些收尾的工做,好比關閉文件描述符等等。
  源碼中體現以下,首先是uv_close:
void uv_close(uv_handle_t* handle, uv_close_cb cb) {
    // 不少代碼...
 
    case UV_PREPARE:
      uv_prepare_stop((uv_prepare_t*)handle);
      uv__handle_closing(handle);
      uv_want_endgame(loop, handle);
      return;
}複製代碼
  uv_close方法除了作關閉handle的本職工做,在最後都會調用一個uv_want_endgame方法收尾,這個方法是一個靜態方法。
INLINE static void uv_want_endgame(uv_loop_t* loop, uv_handle_t* handle) {
  if (!(handle->flags & UV_HANDLE_ENDGAME_QUEUED)) {
    handle->flags |= UV_HANDLE_ENDGAME_QUEUED;

    handle->endgame_next = loop->endgame_handles;
    loop->endgame_handles = handle;
  }
}複製代碼
  內容十分簡單,將handle插入到endgame_handles這個鏈表的表頭。
  最後,只須要看一眼uv_process_endgames便可。
INLINE static void uv_process_endgames(uv_loop_t* loop) {
  uv_handle_t* handle;

  while (loop->endgame_handles) {
    handle = loop->endgame_handles;
    loop->endgame_handles = handle->endgame_next;

    handle->flags &= ~UV_HANDLE_ENDGAME_QUEUED;

    switch (handle->type) {
      case UV_TCP:
        uv_tcp_endgame(loop, (uv_tcp_t*) handle);
        break;
      // ...
    }
  }
}複製代碼
  也很簡潔明瞭,不停的取出endgame_handles鏈表中的handle,依次調用不一樣的callbacks便可。


Run idle hanldes、Run prepare handles、Run check handles
  這三個雖然名字不同,可是主要做用相似,只是在調用順序上有所不一樣。
  因爲Poll for I/O是一個比較特殊的操做,因此這裏提供prepare、check兩個鉤子函數能夠在這個事務先後進行一些別的調用,大能夠用vue的鉤子函數created、mounted來幫助理解。
  idle除去調用較早,也影響poll for I/O這個操做的阻塞時間timeout,官網原文: If there are any idle handles active, the timeout is 0.正常狀況下事件輪詢會根據狀況計算一個阻塞時間timout來決定poll for I/O操做的時間。
  這裏用一個C++例子來證實調用順序,忽略上面的宏,直接看main函數,特別簡單!!!
#include <iostream>
#include "uv.h"
using namespace std;

void idle_callback(uv_idle_t* idle);
void prepare_callback(uv_prepare_t* prepare);
void check_callback(uv_check_t* check);

#define RUN_HANDLE(type) \ do { \ uv_##type##_t type; \ uv_##type##_init(loop, &type); \ uv_##type##_start(&type, type##_callback); \ } while(0)

#define CALLBACK(type) \ do { \ cout << "Run " << #type << " handles" << endl; \ uv_##type##_stop(type); \ } while(0)

#define OPEN(PATH, callback) \ do { \ uv_fs_t req; \ uv_fs_open(loop, &req, PATH, O_RDONLY, 0, callback); \ uv_fs_req_cleanup(&req); \ } while(0)

void idle_callback(uv_idle_t* idle) { CALLBACK(idle); }
void prepare_callback(uv_prepare_t* prepare) { CALLBACK(prepare); }
void check_callback(uv_check_t* check) { CALLBACK(check); }
void on_open(uv_fs_t* req) { cout << "poll for I/O" << endl; }

int main(int argc, const char * argv[]) {
    auto loop = uv_default_loop();
    
    RUN_HANDLE(check);
    RUN_HANDLE(prepare);
    RUN_HANDLE(idle);
    
    OPEN("/Users/feilongpang/workspace/i.js", on_open);
    
    uv_run(loop, UV_RUN_DEFAULT);
    uv_loop_close(loop);
    return 0;
}複製代碼
  執行的時候還發現了一個問題,若是不提供一個I/O操做,Run check handles那一步是會直接跳過,因此手動加了一個open操做。
  能夠看到,我特地調整了callback的添加順序,可是輸出依然是:
  因此,代碼確實是按照官網示例所給的圖順序來執行。
  剩下兩個poll for I/O、pending callbacks留到下一篇講吧。
相關文章
相關標籤/搜索