[譯] libuv 設計概述

evcb8621 8年前發布 | 37K 次閱讀 libuv Node.js擴展

概述

libuv 最初是為 Node.js 所作的跨平臺庫。它基于事件驅動的異步 I/O 模型。

libuv 不僅僅只提供了對于不同 I/O 輪詢機制的簡單抽象:“句柄(handles)”和“流(streams)”也提供了對于 socket 和其他相關實例的高度抽象。同時 libuv 還提供了跨平臺文件 I/O 接口和多線程接口等等。

下圖展示了 libuv 的不同組成部分,以及與這些部分相關的子模塊:

句柄(handles)和請求(requests)

為了能使用戶介入事件循環(event loop),libuv 為用戶提供了兩個抽象:句柄和請求。

句柄表示一個在其被激活時可以執行某些操作且持久存在的對象。例如:當一個預備句柄(prepare handle)處于激活時,它的回調函數會在每次事件循環中被調用;每當一個新 TCP 連接來到時,一個 TCP 服務器句柄的連接回調函數就會被調用。

請求(通常)表示一個短暫存在的操作。這些操作可以操作于句柄,例如寫請求(write requests)用于向一個句柄寫入數據。但是又如 getaddrinfo 請求則不依賴于一個句柄,它們直接在事件循環上執行。

事件循環

事件循環是 libuv 的核心部分。它為所有的 I/O 操作建立了上下文,并且執行于一個單線程中。你可以在多個不同的線程中運行多個事件循環。除非另有說明,不然 libuv 的事件循環(以及其他循環或句柄提供的 API) 并不是線程安全的

事件循環遵循著普遍的單線程異步 I/O 行為:所有的(網絡)I/O 體現在非阻塞的 socket 上,對于不同的平臺,libuv 會選取最佳的輪詢機制:Linux 上為 epoll ,OSX 和其他 BSD 上為 kqueue ,SunOS 上為 event ports , Windows 上則為 IOCP 。作為循環迭代的一部分,事件循環會阻塞并等待被添加的 socket 上 I/O 活動的發生。然后根據當前的 socket 情況(可讀,可寫,掛起)觸發相應的回調函數。所以,一個句柄是可以執行讀操作,寫操作或其他 I/O 行為。

為了能更好的理解事件循環是如何工作的,下圖展示了事件循環一次迭代的所有過程:

  1. 事件循環中的“現在時間(now)”被更新。事件循環會在一次循環迭代開始的時候緩存下當時的時間,用于減少與時間相關的系統調用次數。

  2. 如果事件循環仍是存活(alive)的,那么迭代就會開始,否則循環會立刻退出。如果一個循環內包含激活的可引用句柄,激活的請求或正在關閉的句柄,那么則認為該循環是存活的。

  3. 執行超時定時器(due timers)。所有在循環的“現在時間”之前超時的定時器都將在這個時候得到執行。

  4. 執行等待中回調(pending callbacks)。正常情況下,所有的 I/O 回調都會在輪詢 I/O 后立刻被調用。但是有些情況下,回調可能會被推遲至下一次循環迭代中再執行。任何上一次循環中被推遲的回調,都將在這個時候得到執行。

  5. 執行閑置句柄回調(idle handle callbacks)。盡管它有個不怎么好聽的名字,但只要這些閑置句柄是激活的,那么在每次循環迭代中它們都會執行。

  6. 執行預備回調(prepare handle)。預備回調會在循環為 I/O 阻塞前被調用。

  7. 開始計算輪詢超時(poll timeout)。在為 I/O 阻塞前,事件循環會計算它即將會阻塞多長時間。以下為計算該超時的規則:

    • 如果循環帶著 UV_RUN_NOWAIT 標識執行,那么超時將會是 0 。

    • 如果循環即將停止( uv_stop() 已在之前被調用),那么超時將會是 0 。

    • 如果循環內沒有激活的句柄和請求,那么超時將會是 0 。

    • 如果循環內有激活的閑置句柄,那么超時將會是 0 。

    • 如果有正在等待被關閉的句柄,那么超時將會是 0 。

    • 如果不符合以上所有,那么該超時將會是循環內所有定時器中最早的一個超時時間,如果沒有任何一個激活的定時器,那么超時將會是無限長(infinity)。

  8. 事件循環為 I/O 阻塞。此時事件循環將會為 I/O 阻塞,持續時間為上一步中計算所得的超時時間。所有與 I/O 相關的句柄都將會監視一個指定的文件描述符,等待一個其上的讀或寫操作來激活它們的回調。

  9. 執行檢查句柄回調(check handle callbacks)。在事件循環為 I/O 阻塞結束后,檢查句柄的回調將會立刻執行。檢查句柄本質上是預備句柄的對應物(counterpart)。

  10. 執行關閉回調(close callbacks)。如果一個句柄通過調用 uv_close() 被關閉,那么這將會調用關閉回調。

  11. 盡管在為 I/O 阻塞后可能并沒有 I/O 回調被觸發,但是仍有可能這時已經有一些定時器已經超時。若事件循環是以 UV_RUN_ONCE 標識執行,那么在這時這些超時的定時器的回調將會在此時得到執行。

  12. 迭代結束。如果循環以 UV_RUN_NOWAIT 或 UV_RUN_ONCE 標識執行,迭代便會結束,并且 uv_run() 將會返回。如果循環以 UV_RUN_DEFAULT 標識執行,那么如果若它還是存活的,它就會開始下一次迭代,否則結束。

重要:雖然 libuv 的異步文件 I/O 操作是通過線程池實現的,但是網絡 I/O 總是在單線程中執行的。

注意:雖然在不同平臺上使用的輪詢機制不同,但 libuv 的執行模型在不同平臺下都是保持一致。

文件 I/O

與網絡 I/O 不同,并不存在 libuv 可以依靠的各特定平臺下的文件 I/O 基礎函數,所以目前的實現是在線程中執行阻塞的文件 I/O 操作來模擬異步。

更多關于跨平臺異步文件 I/O 操作的內容,可參閱 此文

libuv 目前使用了一個全局的線程池,所有的循環都可以往其中加入任務。目前有三種操作會在這個線程池中執行:

  • 文件系統操作

  • DNS 函數(getaddrinfo 和 getnameinfo)

  • 通過 uv_queue_work() 添加的用戶代碼

注意:更多關于 libuv 線程池的信息請參閱 此文 。請牢記線程池的大小是有限的。

 

來自: https://segmentfault.com/a/1190000005873917

 

 本文由用戶 evcb8621 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!