Libev原始碼分析 -- 整體設計
libev是Marc Lehmann用C寫的高效能事件迴圈庫。通過libev,可以靈活地把各種事件組織管理起來,如:時鐘、io、訊號等。libev在業界內也是廣受好評,不少專案都採用它來做底層的事件迴圈。node.js也是其中之一。 學習和分析libev庫,有助於理解node.js底層的工作原理,同時也可以學習和借鑑libev的設計思想。本文是最近在學習libev原始碼的一些心得總結吧。
libev示例
先上一個例子,看看libev是怎麼使用的吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
這是libev官網文件的例子,其中libev的使用步驟還是比較清晰的。從main開始入手,可以發現程式碼中主要做了這麼幾件事情:
-
獲取ev_loop例項。ev_loop,從名字上可以看出,它代表了一個事件迴圈,也是我們後面程式碼的主要組織者。
-
建立和初始化watcher。libev中定義了一系列的watcher,每類watcher負責一類特定的事件。一般可以通過ev_TYPE_init函式來建立一個watcher例項(TYPE是某一種watcher型別,如:io, timer等)。例子中分別建立了io和timer兩個watcher,並繫結了相應的回撥函式。當感興趣的事件發生後,對應的回撥函式將會被呼叫。
-
將watcher註冊到ev_loop中。一般可以通過ev_TYPE_start函式來完成。註冊成功後,watcher便和loop關聯起來了,當loop中檢測到感興趣的事件發生,便會通知相關的watcher。
-
啟動事件迴圈。 即後面的ev_run函式。事件迴圈啟動後,當前執行緒/程式將會被阻塞,直到迴圈被終止。
在上面的例子中,在兩個回撥函式中的ev_break函式就是終止迴圈的地方。當5.5秒超時或是標準輸入有輸入事件,則會進入到相應的回撥函式,然後會終止事件迴圈,退出程式。
libev工作原理
總的來看,libev其實是實現了Reactor模式。當中主要包含了這麼幾個角色:watcher, ev_loop和ev_run。
watcher
watcher是Reactor中的Event Handler。一方面,它向事件迴圈提供了統一的呼叫介面(按型別區分);另一方面,它是外部程式碼的注入口,維護著具體的watcher資訊,如:繫結的回撥函式,watcher的優先順序,是否啟用等。
在ev.h中我們可以看到各種watcher的定義,如:ev_io, ev_timer等。其中,watcher的公共屬性定義如下:
1 2 3 4 5 6 7 |
|
其中的巨集定義如下:
1 2 3 |
|
-
active: 表示當前watcher是否被啟用。ev_TYPE_start呼叫後置位,ev_TYPE_stop呼叫後復位;
-
pending: 表示當前watcher有事件就緒,等待處理。pending的值其實就是當前watcher在pendings佇列中的下標;
-
priority: 是當前watcher的優先順序;
-
data: 附加資料指標,用來在watcher中攜帶額外所需的資料;
-
cb:是事件觸發後的回撥函式定義。
具體的watcher再在此基礎上新增額外的屬性。 開發者可以根據需要,選擇特定型別的watcher,建立例項並進行初始化,然後將例項註冊到loop中即可。libev中定義了若干種型別的watcher,每類watcher負責解決某一特定領域的問題(如:io, timer, signal等),可以在ev.h中看到這些watcher的定義。
ev_loop
ev_loop則是一個Reactor的角色,是事件迴圈的上下文環境,就像一根竹籤,把前面的watcher例項像糖葫蘆一樣串起來。
ev_loop的定義
ev_loop的定義在ev.c中,具體如下:
1 2 3 4 5 6 7 8 |
|
ev_vars.h中定義了ev_loop的各種屬性。在ev_wrap.h中則定義了與loop相關的各種巨集,程式碼中大多都是以巨集的形式進行操作。
watcher的管理
在ev_loop中,watcher按各自的型別進行分類儲存。如:io的loop->anfds,timer的loop->timers。ev_TYPE_start在啟用watcher後,便將它加入到相應的儲存結構中(具體的實現在後面介紹watcher的文章再分析)。
在事件迴圈中,有事件就緒的watcher會被挑揀出來,儲存到ev_loop中。這些就緒的watcher主要由loop->pendings和loop->pendingcnt來維護(如下圖所示)。這兩個東西都是二維陣列,第一維都是優先順序。pendings中存的是ANPENDING例項,後者的做要作用就是維護就緒的watcher指標; 而pendingcnt中存的則是對應優先順序上的pendings元素的數量。在每個就緒的watcher上也會有一個pending欄位記錄它在pendings列表中的下標,這樣就可以通過watcher很方便的找到它在pendings列表中的位置了,這對刪除操作很有幫助。
在一輪事件迴圈結束後,則會根據優先順序,依次觸發就緒的watcher。
bool ev_run(loop, flag)
ev_run函式是執行事件迴圈的引擎,即Reactor模式中的select方法。通過向ev_run函式傳遞一個ev_loop例項,便可以開啟一個事件迴圈。ev_run實際上是一個巨大的do-while迴圈,期間會檢查loop中註冊的各種watcher的事件。如果有事件就緒,則觸發相應的watcher。這個迴圈會一直持續到ev_break被呼叫或者無active的watcher為止。當然,也可以通過傳遞EVRUN_NOWAIT或EVRUN_ONCE等flag來控制迴圈的阻塞行為。
ev_run的工作內容,在官方文件的API中有詳細說明,通過文件有助於對ev_run的理解。具體的程式碼有點長,在這裡就不貼了,感興趣的同學可以在ev.c中檢視ev_run的實現程式碼。剔除掉條件檢查和一些無關緊要的程式碼,主要的流程如下圖所示。
可以看到,ev_run的主要工作就是按watcher的分類,先後檢查各種型別的watcher上的事件,通過ev_feed_event函式將就緒的watcher加入到pending的資料結構中。最後呼叫ev_invoke_pending,觸發pending中的watcher。完了以後會檢查,是否還有active的watcher以及是否有ev_break呼叫過,然後決定是否要繼續下一輪迴圈。
總結
總的來看,libev的結構設計還是非常清晰。如果說,主迴圈ev_run是libev這棵大樹的主幹,那麼功能強大,數量繁多的watcher就是這棵大樹的樹葉,而迴圈上下文ev_loop則是連線主幹和樹葉的樹枝,它們的分工與職責是相當明確的。作為大樹的主幹,ev_run的程式碼是非常穩定和乾淨的,基本上不會摻雜外部開發者的邏輯程式碼進來。作為葉子的watcher,它的定位也非常明確:專注於自己的領域,只解決某一個型別的問題。不同的watcher以及watcher和主迴圈之間並沒有太多的干擾和耦合,這也是libev能如此靈活擴充套件的原因。而中間的樹枝ev_loop的作用也是不言而喻的,正是因為它在中間的調和,前面兩位哥們才能活得這麼有個性。
看到這裡,libev的主體架構已經比較清楚了,但是似乎還沒看到與效能相關的關鍵程式碼。與主幹程式碼不一樣,這些程式碼更多的是隱藏在實現具體邏輯的地方,也就是watcher之中的。雖然watcher的使用介面都比較相似,但是不同的watcher,底層的資料結構和處理策略還是不一樣的。下面一篇文章我們就來探索一下libev中比較常用的幾種watcher的設計與實現。
相關文章
- Mybatis原始碼分析-整體設計(一)MyBatis原始碼
- 淺析 及整體分析 Relay 原始碼原始碼
- 微信MMKV原始碼分析(一) | 整體流程原始碼
- jQuery原始碼分析系列 : 整體架構jQuery原始碼架構
- vue-router原始碼分析-整體流程Vue原始碼
- 精盡 MyBatis 原始碼分析 - 整體架構MyBatis原始碼架構
- OkHttp 3.7原始碼分析(一)——整體架構HTTP原始碼架構
- vue-loader 原始碼解析系列之 整體分析Vue原始碼
- RPC框架整體架構設計分析RPC框架架構
- OkHttp 原始碼剖析系列(二)——攔截器整體流程分析HTTP原始碼
- thrift原始碼分析-架構設計原始碼架構
- jQuery整體架構原始碼解析jQuery架構原始碼
- 集體智慧程式設計-原始碼程式設計原始碼
- 併發程式設計—— FutureTask 原始碼分析程式設計原始碼
- Java 原始碼分析 — String 的設計Java原始碼
- Scrapy原始碼閱讀分析_1_整體框架和流程介紹原始碼框架
- 淺析 <路印協議--Loopring> 及整體分析 Relay 原始碼協議OOP原始碼
- 併發程式設計之 Exchanger 原始碼分析程式設計原始碼
- java 併發程式設計-AQS原始碼分析Java程式設計AQS原始碼
- 巢狀滾動設計和原始碼分析巢狀原始碼
- 併發程式設計之 CountDown 原始碼分析程式設計原始碼
- 併發程式設計之 CyclicBarrier 原始碼分析程式設計原始碼
- MASA Framework - 整體設計思路Framework
- 【Mybatis原始碼解析】- 整體架構及原理MyBatis原始碼架構
- tomcat原始碼分析(第一篇 從整體架構開始)Tomcat原始碼架構
- Java 併發程式設計(十五) -- Semaphore原始碼分析Java程式設計原始碼
- Java 併發程式設計(十四) -- CyclicBarrier原始碼分析Java程式設計原始碼
- Java 併發程式設計(十三) -- CountDownLatch原始碼分析Java程式設計CountDownLatch原始碼
- Java 併發程式設計(七) -- AbstractQueuedSynchronizer 原始碼分析Java程式設計原始碼
- 併發程式設計 —— Timer 原始碼分析程式設計原始碼
- 設計模式(十四)——模板模式(SpringIOC原始碼分析)設計模式Spring原始碼
- Java非同步程式設計——深入原始碼分析FutureTaskJava非同步程式設計原始碼
- 併發程式設計之 Condition 原始碼分析程式設計原始碼
- 併發程式設計之 SynchronousQueue 核心原始碼分析程式設計原始碼
- 併發程式設計之——寫鎖原始碼分析程式設計原始碼
- C++ STL 記憶體配置的設計思想與關鍵原始碼分析C++記憶體原始碼
- C++STL記憶體配置的設計思想與關鍵原始碼分析C++記憶體原始碼
- C++ STL記憶體配置的設計思想與關鍵原始碼分析C++記憶體原始碼