[譯] libuv 設計概述

概述

libuv 最初是爲 Node.js 所做的跨平臺庫。它基於事件驅動的異步 I/O 模型。html

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

下圖展現了 libuv 的不一樣組成部分,以及與這些部分相關的子模塊:安全

architecture.png

句柄(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 行爲。socket

爲了能更好的理解事件循環是如何工做的,下圖展現了事件循環一次迭代的全部過程:async

loop_iteration.png

  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_NOWAITUV_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 線程池的信息請參閱此文。請牢記線程池的大小是有限的。

最後

原文連接:http://docs.libuv.org/en/v1.x/design.html

相關文章
相關標籤/搜索