Libuv初理解

綠箭字益達發表於2018-03-13

整體概念

libuv是一個跨平臺、專門寫給nodejs的庫,它的設計是圍繞著事件驅動的非同步I/O模型。在不同的I/O輪詢機制上,libuv提供的不是簡單抽象物件:handle和streams為sockets和其他例項提供了一個高階抽象。此外,libuv還提供了跨平臺檔案I/O和執行緒化功能

這裡有一個圖表,說明了構成libuv的不同部分以及它們與什麼子系統相關:

libuv

handles和request

libuv給使用者提供了2個可操作的抽象物件,evnt loop的組合:handles和requests。

handles能夠操作那些長時間活躍的物件,例如:

  • 活躍的handle在每一次event loop期間會被呼叫一次迴圈迭代
  • tcp服務會回撥一次連線服務當一個新的連線過來時

requests代表的是短時間活躍的操作。這些操作可以被表現依賴於handle上:寫請求用來在handle上寫資料。或者獨立的:像getaddrinfo請求就不需要依賴handle,可以自己獨立在event loop中執行

I/O迴圈

I/O或者說event loop是libuv的核心。它為所有I/O操作建立了內容,也就意味著這些操作繫結在了某一個單一的執行緒上。只要每個執行緒執行在不同的執行緒中,就可以執行多個事件迴圈。不過libuv的event loop不是一個安全的執行緒

event loop遵從一個單一非同步I/O執行緒方法:所有的(網路)I/O必須執行在一個沒有被阻塞的sockets中,使用給定平臺上可用的最佳機制進行輪詢(例如:Linux的epoll、OSX的kqueue和其他的一些BSDs、SunOs的事件介面和windows上的IOCP)。作為迴圈迭代的一部分,loop會被阻塞,等待已經新增到poller和回撥的sockets上的I/O活動,此時將觸發sockets狀態(可讀、可寫的掛起),因此handle可以讀取、寫入或執行所需的I/O操作。

為了更好的理解event loop的操作,下圖將展示所有迴圈迭代(loop iteration)的狀態

迴圈迭代狀態圖

1.“now”的迴圈概念得到了更新。事件迴圈在事件迴圈開始時快取當前時間,以減少與時間相關的系統呼叫的數量。

2.如果迴圈是活躍的,則會啟動迴圈迭代,否則迴圈將立即退出。那麼,什麼時候被認為是活躍著的迴圈呢?如果一個迴圈有活動和ref自己的handles,主動請求或關閉控制程式碼被認為是有活躍著的。

3.由於計時器執行。所有活躍著的時間排程器都安排在迴圈的now概念之前呼叫他們的回撥函式

4.掛載的回撥函式將被呼叫。所有I/O回撥函式都是在輪詢event loop的poll之後被呼叫的。但是,存在一些特殊情況:比如呼叫這樣的回撥被推遲到下一個迴圈迭代中呼叫,那麼此時就可能立刻執行上一個event loop中延遲的I/O回撥函式。

5.呼叫idle handle回撥函式被呼叫。儘管這個名稱不是很好聽,但如果idle handle是活躍的,那麼再每次event loop過程中都一定會呼叫

6.呼叫prepare handle的回撥函式。Prepare handles的回撥函式在I/O之前

7.poll階段計算延遲時間。在阻塞I/O之前,loop去計算它應該阻塞多長時間。這些是計算超時時的規則:

  • 如果loop執行的flag是UV_RUN_NOWAIT,延遲時間為0
  • 如果loop是被uv_stop阻塞,則延遲0
  • 如果此時沒有活躍的handles和requests,延遲為0
  • 如果此時有任何活躍著的idel handles,則延遲0
  • 如果此時有任何處於pending狀態的handles被關閉,則延遲為0
  • 如果上述任何情況都沒有,則呼叫最近的timeout,如果沒有任何活躍的時間timer存在,則無限延遲

8.在I/O階段阻塞。在這個時候,the loop將阻塞I/O,用於在上一步中計算的持續時間。所有正在監視一個讀或寫操作的給定檔案描述符的I/O相關handles將在此處呼叫它們的回撥。

9.呼叫check handle的回撥。check handle在I/O階段之後呼叫。

10.呼叫CLose的回撥函式。如果一個handes被uv_close()函式呼叫關閉,那麼close的回撥函式就會被呼叫

11.在使用UV_RUN_ONCE模式這個特殊情況下執行,I/O可能沒有回撥函式存在在I/O被阻塞之後去呼叫,而有timers到期,從而回撥了timers的回撥函式

12.迴圈結束。如果loop是執行在UV_RUN_NOWAIT或者UV_RUN_ONCE模式下的執行結束,那麼就uv_run()方法會被返回。如果執行在UV_RUN_DEFAULT模式下,且the loop在結束的時候仍然活著,那麼將會繼續執行,並且從頭開始迴圈迭代,否則也將會結束

在每次迴圈執行緒中,libuv使用執行緒池實現非同步檔案I/O操作,但是網路I/O總是在單個執行緒中執行。

I/O檔案

不像網路I/O,沒有特定於平臺的檔案I/O原語libuv可以依賴,所以當前的方法是線上程池中執行阻塞檔案I/O操作。

libuv目前使用的是一個全域性執行緒池,其中所有迴圈都可以在其中進行佇列工作。3種操作目前在此池中執行:

檔案系統操作 DNS功能(getaddrinfo和getnameinfo) 使用者通過uv_queue_work()指定的程式碼

相關文章