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

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

這篇應該能結,簡圖如下。 上一篇講到了uv__work_submit方法,接著寫了。 從post開始。 wq就是上一篇講的線程都會用到的那個隊列,這裡負責插入任務,worker中取出任務。 沒想到post到這裡沒了,這點東西併到上一篇就好了。以後寫這種系列博客還是先規劃一下,不能邊看源碼邊寫…… 函 ...


  這篇應該能結,簡圖如下。

  上一篇講到了uv__work_submit方法,接著寫了。

void uv__work_submit(uv_loop_t* loop,
                     struct uv__work* w,
                     enum uv__work_kind kind,
                     void (*work)(struct uv__work* w),
                     void (*done)(struct uv__work* w, int status)) {
  // 上篇主要講的這裡 初始化線程池等
  uv_once(&once, init_once);
  w->loop = loop;
  w->work = work;
  w->done = done;
  post(&w->wq, kind);
}

  從post開始。

static void post(QUEUE* q, enum uv__work_kind kind) {
  // 因為存在隊列插入操作 需要加鎖
  uv_mutex_lock(&mutex);
  if (kind == UV__WORK_SLOW_IO) {
    //跳...
  }

  QUEUE_INSERT_TAIL(&wq, q);
  // 如果有空閑線程 喚醒
  if (idle_threads > 0)
    uv_cond_signal(&cond);
  uv_mutex_unlock(&mutex);
}

  wq就是上一篇講的線程都會用到的那個隊列,這裡負責插入任務,worker中取出任務。

  沒想到post到這裡沒了,這點東西併到上一篇就好了。以後寫這種系列博客還是先規劃一下,不能邊看源碼邊寫……

  函數到這裡就斷了,看似沒有線索,實際上在上一節的worker方法中,還漏了一個地方。

static void worker(void* arg) {
  // ...
  for (;;) {
    // 這裡調用內部fs方法處理任務
    w = QUEUE_DATA(q, struct uv__work, wq);
    w->work(w);

    uv_mutex_lock(&w->loop->wq_mutex);
    w->work = NULL; 
    QUEUE_INSERT_TAIL(&w->loop->wq, &w->wq);
    // 這個是漏了的關鍵
    uv_async_send(&w->loop->wq_async);
    uv_mutex_unlock(&w->loop->wq_mutex);

    // ...
  }
}

  每一條線程在每次處理完一條事務並將其插入工作隊列wq後,都會調用一下這個uv_async_send方法,上一篇沒講這個。

  這裡的wq_async是一個在loop上面的變數,在輪詢初始化的時候出現過,這裡先不看。

  uv_async_send這個方法又涉及到另外一個大模塊,如下。

int uv_async_send(uv_async_t* handle) {
  // 錯誤處理...
  if (!uv__atomic_exchange_set(&handle->async_sent)) {
    POST_COMPLETION_FOR_REQ(loop, &handle->async_req);
  }

  return 0;
}

// 將操作結果推到iocp上面
#define POST_COMPLETION_FOR_REQ(loop, req)                              \
  if (!PostQueuedCompletionStatus((loop)->iocp,                         \
                                  0,                                    \
                                  0,                                    \
                                  &((req)->u.io.overlapped))) {         \
    uv_fatal_error(GetLastError(), "PostQueuedCompletionStatus");       \
  }

  這個地方說實話我並不是明白windows底層API的操作原理,IOCP這部分我沒有去研究,只能從字面上去理解。

  關於PostXXX方法官網解釋如下:

Posts an I/O completion packet to an I/O completion port.

  將一個I/O完成的數據打包到I/O完成的埠,翻譯過來就是這樣,個人理解上的話大概是把一個async_req丟到IOCP那裡保存起來。

 

  接下來終於可以回到事件輪詢部分,點題了。

int uv_run(uv_loop_t *loop, uv_run_mode mode) {
  // ...

  while (r != 0 && loop->stop_flag == 0) {
    // ...
    // call pending callbacks
    ran_pending = uv_process_reqs(loop);
    // ...
    // poll for I/O
    if (pGetQueuedCompletionStatusEx)
      uv__poll(loop, timeout);
    else
      uv__poll_wine(loop, timeout);
    // ...
  }
  // ...
}

  截取了剩下的poll for I/O、call pending callback,也就是剩下的兩部分了。if判斷不用管,只是一個方法相容,最終的目的是一樣的。

  所以只看uv__poll部分。

static void uv__poll(uv_loop_t* loop, DWORD timeout) {
  // ...
  // 設定阻塞時間
  uint64_t timeout_time;
  timeout_time = loop->time + timeout;

  for (repeat = 0; ; repeat++) {
    success = GetQueuedCompletionStatusEx(loop->iocp,
                                          overlappeds,
                                          ARRAY_SIZE(overlappeds),
                                          &count,
                                          timeout,
                                          FALSE);

    if (success) {
      for (i = 0; i < count; i++) {
        if (overlappeds[i].lpOverlapped) {
          req = uv_overlapped_to_req(overlappeds[i].lpOverlapped);
          uv_insert_pending_req(loop, req);
        }
      }
      uv_update_time(loop);
    } else if (GetLastError() != WAIT_TIMEOUT) {
      // ...
    } else if (timeout > 0) {
      // 超時處理...
    }
    break;
  }
}

  這裡的GetQueueXXX方法與之前的PostQueueXXX正好是一對方法,都是基於IOCP,一個是存儲,一個是取出。

  遍歷操作就很容易懂了,取出數據後,一個個的塞到pending callback的隊列中。

  把uv_insert_pending_req、uv_process_reqs兩個方法結合起來看。

INLINE static void uv_insert_pending_req(uv_loop_t* loop, uv_req_t* req) {
  req->next_req = NULL;
  // 插入到pending_reqs_tail上
  if (loop->pending_reqs_tail) {
    // DEBUG...

    req->next_req = loop->pending_reqs_tail->next_req;
    loop->pending_reqs_tail->next_req = req;
    loop->pending_reqs_tail = req;
  } else {
    req->next_req = req;
    loop->pending_reqs_tail = req;
  }
}

INLINE static int uv_process_reqs(uv_loop_t* loop) {
  // ...

  // 處理pending_reqs_tail
  first = loop->pending_reqs_tail->next_req;
  next = first;
  loop->pending_reqs_tail = NULL;

  while (next != NULL) {
    req = next;
    next = req->next_req != first ? req->next_req : NULL;

    switch (req->type) {
      // handle各類req...
    }
  }

  return 1;
}

  就這樣,完美的把poll for I/O與call pending callback兩塊內容連接到了一起,也同時理解了一個非同步I/O操作是如何在node內部被處理的。

 

  最後還是剩一個尾巴,就是丟到IOCP的那個async_req怎麼回事?這個變數在輪詢的初始化方法中出現,如下。

typedef struct uv_loop_s uv_loop_t;

struct uv_loop_s {
  // ...
  UV_LOOP_PRIVATE_FIELDS
};

#define UV_LOOP_PRIVATE_FIELDS                                                \
  // 其餘變數
  uv_async_t wq_async;

// uv__word_done是這個handle的回調函數
int uv_loop_init(uv_loop_t* loop) {
  // ...
  err = uv_async_init(loop, &loop->wq_async, uv__work_done);
  // ...
}

// 第一篇中演示過handle的初始化和運行 很常規的init、start兩步
int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) {
  uv_req_t* req;

  uv__handle_init(loop, (uv_handle_t*) handle, UV_ASYNC);
  handle->async_sent = 0;
  handle->async_cb = async_cb;

  req = &handle->async_req;
  UV_REQ_INIT(req, UV_WAKEUP);
  req->data = handle;

  uv__handle_start(handle);

  return 0;
}

# define UV_REQ_INIT(req, typ)                                                \
  do {                                                                        \
    (req)->type = (typ);                                                      \
  }                                                                           \
  while (0)

  從代碼裡面可以知道,loop上本身帶有一個uv_async_t的變數wq_async,初始化後有四個屬性。其中需要註意,這個類型的type被設置為UV_WAKEUP。

  再回到uv_process_reqs中,處理從IOCP取出的req那塊。

INLINE static int uv_process_reqs(uv_loop_t* loop) {
  // ...

  while (next != NULL) {
    // ...
    switch (req->type) {
      // ...
      case UV_WAKEUP:
        uv_process_async_wakeup_req(loop, (uv_async_t*) req->data, req);
        break;
      // ...
    }
  }

  return 1;
}

  我們找到了處理UV_WAKEUP的case,參數參考上面那個初始化的代碼也很容易得知,req->data就是loop初始化的那個handle,req是那個async_req。

  方法代碼如下。

void uv_process_async_wakeup_req(uv_loop_t* loop, uv_async_t* handle, uv_req_t* req) {
  // 丟進IOCP的時候被設置為1了 具體在uv_async_send的uv__atomic_exchange_set方法中
  handle->async_sent = 0;
  if (handle->flags & UV_HANDLE_CLOSING) {
    uv_want_endgame(loop, (uv_handle_t*)handle);
  } else if (handle->async_cb != NULL) {
    // 進的else分支
    handle->async_cb(handle);
  }
}

  這裡的async_cb也是初始化就定義了,實際函數名是uv__work_done。

void uv__work_done(uv_async_t* handle) {
  // ...

  loop = container_of(handle, uv_loop_t, wq_async);
  uv_mutex_lock(&loop->wq_mutex);
  // 還是那個熟悉的隊列
  QUEUE_MOVE(&loop->wq, &wq);
  uv_mutex_unlock(&loop->wq_mutex);

  while (!QUEUE_EMPTY(&wq)) {
    // ...
    w->done(w, err);
  }
}

  這個done,就是用戶從JS傳過去的callback……

  也就是說call pending callback實際上是調用用戶傳過來的callback,第二篇的圖其實是有問題的,系列完結撒花!


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

-Advertisement-
Play Games
更多相關文章
  • Egg.js 簡介 Egg.js 為企業級框架和應用而生 ,幫助開發團隊和開發人員降低開發和維護成本。 專註於提供 Web 開發的核心功能和一套靈活可擴展的插件機制,不會做出技術選型,因為固定的技術選型會使框架的擴展性變差,無法滿足各種定製需求。 Egg 的插件機制有很高的可擴展性, 一個插件只做一 ...
  • 語義化的主要目的可以概括為用正確的標簽做正確的事。 HTML語義化可以讓頁面的內容結構化,便於瀏覽器解析和搜索引擎解析,並提高代碼的可維護度和可重用性。 儘可能少的使用無語義的標簽<div>,多用語義化標簽<header><section><footer> ...
  • BFC
    BFC(Block formatting context)塊級格式化上下文,是一個獨立的渲染區域。 一、如何觸發BFC: 1、根元素(html); 2、設置float除none意外的值(left ,right); 3、設置overflow 除visible 以外的值(hidden,auto,srol ...
  • 一、html,css部分 二、js部分 三、源代碼部分 ...
  • 一、html,css部分 二、js部分 三、源代碼部分 四、圖片img ...
  • css學習一周後,寫了個基礎博客樣式。 樣式是出來了,但是在寫的過程中感覺css寫的雜亂無章,可能是寫的太少了吧,條例不是很清除,只是在寫的過程 中一個點一個點的套,感覺樣式出來即可,沒有做到由全局出發考慮再到局部,再到細節的考慮。 總共耗時4個小時,寫的時候 html 部分頭腦很清晰,可以做到心中 ...
  • 根據Bootstrap--Grid 中 col-sm-* col-md-* col-lg-* col-xl-*的意義: .col-sm-* 小屏幕 手機 (≥ 576px) .col-md-* 中等屏幕 平板 (≥ 768px) .col-lg-* 大屏幕 桌面顯示器 (≥ 992px) .col- ...
  • 最好的實踐,就是給定一個實踐的目標去實踐。 目標:利用 CSS3 的一些特性,繪製一個魔方,要可以玩轉的那種,即上下左右每一層都可以獨立旋轉。效果如下: 為了完成此效果,將使用到以下相關概念和樣式:坐標、3D呈現、平移、旋轉。 (1)坐標 屏幕的起點坐標是(0,0,0),往右遞增為 x 方向,使用 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...