libuv 最初是爲 Node.js 所做的跨平臺庫。它基於事件驅動的異步 I/O 模型。html
libuv 不只僅只提供了對於不一樣 I/O 輪詢機制的簡單抽象:「句柄(handles)」和「流(streams)」也提供了對於 socket 和其餘相關實例的高度抽象。同時 libuv 還提供了跨平臺文件 I/O 接口和多線程接口等等。緩存
下圖展現了 libuv 的不一樣組成部分,以及與這些部分相關的子模塊:安全
爲了能使用戶介入事件循環(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
事件循環中的「如今時間(now)」被更新。事件循環會在一次循環迭代開始的時候緩存下當時的時間,用於減小與時間相關的系統調用次數。函數
若是事件循環還是存活(alive)的,那麼迭代就會開始,不然循環會馬上退出。若是一個循環內包含激活的可引用句柄,激活的請求或正在關閉的句柄,那麼則認爲該循環是存活的。
執行超時定時器(due timers)。全部在循環的「如今時間」以前超時的定時器都將在這個時候獲得執行。
執行等待中回調(pending callbacks)。正常狀況下,全部的 I/O 回調都會在輪詢 I/O 後馬上被調用。可是有些狀況下,回調可能會被推遲至下一次循環迭代中再執行。任何上一次循環中被推遲的回調,都將在這個時候獲得執行。
執行閒置句柄回調(idle handle callbacks)。儘管它有個不怎麼好聽的名字,但只要這些閒置句柄是激活的,那麼在每次循環迭代中它們都會執行。
執行預備回調(prepare handle)。預備回調會在循環爲 I/O 阻塞前被調用。
開始計算輪詢超時(poll timeout)。在爲 I/O 阻塞前,事件循環會計算它即將會阻塞多長時間。如下爲計算該超時的規則:
若是循環帶着 UV_RUN_NOWAIT
標識執行,那麼超時將會是 0 。
若是循環即將中止(uv_stop()
已在以前被調用),那麼超時將會是 0 。
若是循環內沒有激活的句柄和請求,那麼超時將會是 0 。
若是循環內有激活的閒置句柄,那麼超時將會是 0 。
若是有正在等待被關閉的句柄,那麼超時將會是 0 。
若是不符合以上全部,那麼該超時將會是循環內全部定時器中最先的一個超時時間,若是沒有任何一個激活的定時器,那麼超時將會是無限長(infinity)。
事件循環爲 I/O 阻塞。此時事件循環將會爲 I/O 阻塞,持續時間爲上一步中計算所得的超時時間。全部與 I/O 相關的句柄都將會監視一個指定的文件描述符,等待一個其上的讀或寫操做來激活它們的回調。
執行檢查句柄回調(check handle callbacks)。在事件循環爲 I/O 阻塞結束後,檢查句柄的回調將會馬上執行。檢查句柄本質上是預備句柄的對應物(counterpart)。
執行關閉回調(close callbacks)。若是一個句柄經過調用 uv_close()
被關閉,那麼這將會調用關閉回調。
儘管在爲 I/O 阻塞後可能並無 I/O 回調被觸發,可是仍有可能這時已經有一些定時器已經超時。若事件循環是以 UV_RUN_ONCE
標識執行,那麼在這時這些超時的定時器的回調將會在此時獲得執行。
迭代結束。若是循環以 UV_RUN_NOWAIT
或 UV_RUN_ONCE
標識執行,迭代便會結束,而且 uv_run()
將會返回。若是循環以 UV_RUN_DEFAULT
標識執行,那麼若是若它仍是存活的,它就會開始下一次迭代,不然結束。
重要:雖然 libuv 的異步文件 I/O 操做是經過線程池實現的,可是網絡 I/O 老是在單線程中執行的。
注意:雖然在不一樣平臺上使用的輪詢機制不一樣,但 libuv 的執行模型在不一樣平臺下都是保持一致。
與網絡 I/O 不一樣,並不存在 libuv 能夠依靠的各特定平臺下的文件 I/O 基礎函數,因此目前的實現是在線程中執行阻塞的文件 I/O 操做來模擬異步。
更多關於跨平臺異步文件 I/O 操做的內容,可參閱此文。
libuv 目前使用了一個全局的線程池,全部的循環均可以往其中加入任務。目前有三種操做會在這個線程池中執行:
文件系統操做
DNS 函數(getaddrinfo 和 getnameinfo)
經過 uv_queue_work()
添加的用戶代碼
注意:更多關於 libuv 線程池的信息請參閱此文。請牢記線程池的大小是有限的。