物聯網高併發程式設計之網路程式設計中的執行緒模型
如需瞭解更多物聯網網路程式設計知識請點選: 物聯網雲端開發武器庫
物聯網高併發程式設計之網路程式設計中的執行緒模型
值得說明的是,具體選擇執行緒還是程序,更多是與平臺及程式語言相關。
例如 C 語言使用執行緒和程序都可以(例如 Nginx 使用程序,Memcached 使用執行緒),Java 語言一般使用執行緒(例如 Netty),為了描述方便,下面都使用執行緒來進行描述。
執行緒模型1:傳統阻塞 I/O 服務模型
特點:
- 1)採用阻塞式 I/O 模型獲取輸入資料;
- 2)每個連線都需要獨立的執行緒完成資料輸入,業務處理,資料返回的完整操作。
存在問題:
- 1)當併發數較大時,需要建立大量執行緒來處理連線,系統資源佔用較大;
- 2)連線建立後,如果當前執行緒暫時沒有資料可讀,則執行緒就阻塞在 Read 操作上,造成執行緒資源浪費。
執行緒模型2:Reactor 模式
基本介紹
針對傳統阻塞 I/O 服務模型的 2 個缺點,比較常見的有如下解決方案:
- 1)基於 I/O 複用模型:多個連線共用一個阻塞物件,應用程式只需要在一個阻塞物件上等待,無需阻塞等待所有連線。當某條連線有新的資料可以處理時,作業系統通知應用程式,執行緒從阻塞狀態返回,開始進行業務處理;
- 2)基於執行緒池複用執行緒資源:不必再為每個連線建立執行緒,將連線完成後的業務處理任務分配給執行緒進行處理,一個執行緒可以處理多個連線的業務。
Reactor 模式,是指通過一個或多個輸入同時傳遞給服務處理器的服務請求的事件驅動處理模式。
服務端程式處理傳入多路請求,並將它們同步分派給請求對應的處理執行緒,Reactor 模式也叫 Dispatcher 模式。
即 I/O 多了複用統一監聽事件,收到事件後分發(Dispatch 給某程序),是編寫高效能網路伺服器的必備技術之一。
Reactor 模式中有 2 個關鍵組成:
- 1)Reactor:Reactor 在一個單獨的執行緒中執行,負責監聽和分發事件,分發給適當的處理程式來對 IO 事件做出反應。 它就像公司的電話接線員,它接聽來自客戶的電話並將線路轉移到適當的聯絡人;
- 2)Handlers:處理程式執行 I/O 事件要完成的實際事件,類似於客戶想要與之交談的公司中的實際官員。Reactor 通過排程適當的處理程式來響應 I/O 事件,處理程式執行非阻塞操作。
根據 Reactor 的數量和處理資源池執行緒的數量不同,有 3 種典型的實現:
- 1)單 Reactor 單執行緒;
- 2)單 Reactor 多執行緒;
- 3)主從 Reactor 多執行緒。
單 Reactor 單執行緒
其中,Select 是前面 I/O 複用模型介紹的標準網路程式設計 API,可以實現應用程式通過一個阻塞物件監聽多路連線請求,其他方案示意圖類似。
方案說明:
- 1)Reactor 物件通過 Select 監控客戶端請求事件,收到事件後通過 Dispatch 進行分發;
- 2)如果是建立連線請求事件,則由 Acceptor 通過 Accept 處理連線請求,然後建立一個 Handler 物件處理連線完成後的後續業務處理;
- 3)如果不是建立連線事件,則 Reactor 會分發呼叫連線對應的 Handler 來響應;
- 4)Handler 會完成 Read→業務處理→Send 的完整業務流程。
優點:模型簡單,沒有多執行緒、程序通訊、競爭的問題,全部都在一個執行緒中完成。
缺點:效能問題,只有一個執行緒,無法完全發揮多核 CPU 的效能。Handler 在處理某個連線上的業務時,整個程序無法處理其他連線事件,很容易導致效能瓶頸。
可靠性問題,執行緒意外跑飛,或者進入死迴圈,會導致整個系統通訊模組不可用,不能接收和處理外部訊息,造成節點故障。
使用場景: 客戶端的數量有限,業務處理非常快速,比如 Redis,業務處理的時間複雜度 O(1)。
單 Reactor 多執行緒
方案說明:
- 1)Reactor 物件通過 Select 監控客戶端請求事件,收到事件後通過 Dispatch 進行分發;
- 2)如果是建立連線請求事件,則由 Acceptor 通過 Accept 處理連線請求,然後建立一個 Handler 物件處理連線完成後續的各種事件;
- 3)如果不是建立連線事件,則 Reactor 會分發呼叫連線對應的 Handler 來響應;
- 4)Handler 只負責響應事件,不做具體業務處理,通過 Read 讀取資料後,會分發給後面的 Worker 執行緒池進行業務處理;
- 5)Worker 執行緒池會分配獨立的執行緒完成真正的業務處理,將響應結果發給 Handler 進行處理;
- 6)Handler 收到響應結果後通過 Send 將響應結果返回給 Client。
優點:可以充分利用多核 CPU 的處理能力。
缺點:多執行緒資料共享和訪問比較複雜;Reactor 承擔所有事件的監聽和響應,在單執行緒中執行,高併發場景下容易成為效能瓶頸。
主從 Reactor 多執行緒
針對單 Reactor 多執行緒模型中,Reactor 在單執行緒中執行,高併發場景下容易成為效能瓶頸,可以讓 Reactor 在多執行緒中執行。
方案說明:
- 1)Reactor 主執行緒 MainReactor 物件通過 Select 監控建立連線事件,收到事件後通過 Acceptor 接收,處理建立連線事件;
- 2)Acceptor 處理建立連線事件後,MainReactor 將連線分配 Reactor 子執行緒給 SubReactor 進行處理;
- 3)SubReactor 將連線加入連線佇列進行監聽,並建立一個 Handler 用於處理各種連線事件;
- 4)當有新的事件發生時,SubReactor 會呼叫連線對應的 Handler 進行響應;
- 5)Handler 通過 Read 讀取資料後,會分發給後面的 Worker 執行緒池進行業務處理;
- 6)Worker 執行緒池會分配獨立的執行緒完成真正的業務處理,如何將響應結果發給 Handler 進行處理;
- 7)Handler 收到響應結果後通過 Send 將響應結果返回給 Client。
優點:父執行緒與子執行緒的資料互動簡單職責明確,父執行緒只需要接收新連線,子執行緒完成後續的業務處理。
父執行緒與子執行緒的資料互動簡單,Reactor 主執行緒只需要把新連線傳給子執行緒,子執行緒無需返回資料。
這種模型在許多專案中廣泛使用,包括 Nginx 主從 Reactor 多程序模型,Memcached 主從多執行緒,Netty 主從多執行緒模型的支援。
小結
3 種模式可以用個比喻來理解:(餐廳常常僱傭接待員負責迎接顧客,當顧客入坐後,侍應生專門為這張桌子服務)
- 1)單 Reactor 單執行緒,接待員和侍應生是同一個人,全程為顧客服務;
- 2)單 Reactor 多執行緒,1 個接待員,多個侍應生,接待員只負責接待;
- 3)主從 Reactor 多執行緒,多個接待員,多個侍應生。
Reactor 模式具有如下的優點:
- 1)響應快,不必為單個同步時間所阻塞,雖然 Reactor 本身依然是同步的;
- 2)程式設計相對簡單,可以最大程度的避免複雜的多執行緒及同步問題,並且避免了多執行緒/程序的切換開銷;
- 3)可擴充套件性,可以方便的通過增加 Reactor 例項個數來充分利用 CPU 資源;
- 4)可複用性,Reactor 模型本身與具體事件處理邏輯無關,具有很高的複用性。
執行緒模型2:Proactor 模型
在 Reactor 模式中,Reactor 等待某個事件或者可應用或者操作的狀態發生(比如檔案描述符可讀寫,或者是 Socket 可讀寫)。
然後把這個事件傳給事先註冊的 Handler(事件處理函式或者回調函式),由後者來做實際的讀寫操作。
其中的讀寫操作都需要應用程式同步操作,所以 Reactor 是非阻塞同步網路模型。
如果把 I/O 操作改為非同步,即交給作業系統來完成就能進一步提升效能,這就是非同步網路模型 Proactor。
Proactor 是和非同步 I/O 相關的,詳細方案如下:
- 1)Proactor Initiator 建立 Proactor 和 Handler 物件,並將 Proactor 和 Handler 都通過 AsyOptProcessor(Asynchronous Operation Processor)註冊到核心;
- 2)AsyOptProcessor 處理註冊請求,並處理 I/O 操作;
- 3)AsyOptProcessor 完成 I/O 操作後通知 Proactor;
- 4)Proactor 根據不同的事件型別回撥不同的 Handler 進行業務處理;
- 5)Handler 完成業務處理。
可以看出 Proactor 和 Reactor 的區別:
- 1)Reactor 是在事件發生時就通知事先註冊的事件(讀寫在應用程式執行緒中處理完成);
- 2)Proactor 是在事件發生時基於非同步 I/O 完成讀寫操作(由核心完成),待 I/O 操作完成後才回調應用程式的處理器來進行業務處理。
理論上 Proactor 比 Reactor 效率更高,非同步 I/O 更加充分發揮 DMA(Direct Memory Access,直接記憶體存取)的優勢。
但是Proactor有如下缺點:
- 1)程式設計複雜性,由於非同步操作流程的事件的初始化和事件完成在時間和空間上都是相互分離的,因此開發非同步應用程式更加複雜。應用程式還可能因為反向的流控而變得更加難以 Debug;
- 2)記憶體使用,緩衝區在讀或寫操作的時間段內必須保持住,可能造成持續的不確定性,並且每個併發操作都要求有獨立的快取,相比 Reactor 模式,在 Socket 已經準備好讀或寫前,是不要求開闢快取的;
- 3)作業系統支援,Windows 下通過 IOCP 實現了真正的非同步 I/O,而在 Linux 系統下,Linux 2.6 才引入,目前非同步 I/O 還不完善。
因此在 Linux 下實現高併發網路程式設計都是以 Reactor 模型為主。