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

来源:https://www.cnblogs.com/QH-Jimmy/archive/2019/05/06/10821342.html
-Advertisement-
Play Games

好久沒寫東西了,過了一段鹹魚生活,無意中想起了脈脈上面一句話: 始終保持自己的競爭力。所以,繼續開寫! 一般的JavaScript源碼看的已經沒啥意思了,我也不會寫什麼xx入門新手教程,最終決定還是啃原來的硬骨頭,從外層libuv => node => v8一步步實現原有的目標吧。 libuv核心還 ...


  好久沒寫東西了,過了一段鹹魚生活,無意中想起了脈脈上面一句話: 始終保持自己的競爭力。所以,繼續開寫!

  一般的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 callbacks

  這類回調比較特殊,官網是這麼解釋的: 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留到下一篇講吧。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...