新聞中心
前文送分來了,華為一面,介紹下五種 IO 模型 我們解釋過,操作系統(tǒng)系統(tǒng)如何獲取輸入和輸出的數(shù)據(jù),就是 I/O 模型干的事。

站在用戶的角度思考問題,與客戶深入溝通,找到忻城網(wǎng)站設(shè)計與忻城網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:成都網(wǎng)站建設(shè)、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、國際域名空間、網(wǎng)絡(luò)空間、企業(yè)郵箱。業(yè)務(wù)覆蓋忻城地區(qū)。
那怎么通過輸入數(shù)據(jù)得到的輸出數(shù)據(jù)的?換句話說,操作系統(tǒng)是怎么處理輸入數(shù)據(jù)的(怎么處理請求的)?這就是線程模型(或者說進程模型)的事了。
所以,當我們想要設(shè)計一個服務(wù)端的并發(fā)模型時,主要有如下兩個關(guān)鍵點:
- 服務(wù)器如何管理連接,獲取輸入輸出數(shù)據(jù):基于 「I/O 模型」管理連接
- 服務(wù)器如何處理請求:基于「線程/進程模型」處理請求
值得說明的是,具體選擇線程還是進程來處理請求,更多是與平臺及編程語言相關(guān),例如 Nginx 使用進程,Memcached 使用線程,而 C 語言使用線程和進程都可以,Java 語言一般使用線程(例如 Netty),為方便行文,下文統(tǒng)一用線程模型
主要有三類線程模型:
- 阻塞 I/O 模型(Blocking I/O Model)
- Reactor 模型
單 Reactor 單線程
單 Reactor 多線程
主從 Reactor 多線程
- Proactor 模型
Blocking I/O Model
方案說明
阻塞 I/O 模型也被稱為同步 I/O 模型。在這種模型下,為每一個請求分配一個線程,并且當一個線程執(zhí)行一個 I/O 操作時,它會一直等待,直到 I/O 操作完成并返回結(jié)果。在這個過程中,該線程會被阻塞,無法執(zhí)行其他任務(wù)。
廢話不多說,看圖就明白了:
阻塞 I/O 模型原理示例圖
特點
- 采用「阻塞 I/O 模型」獲取輸入數(shù)據(jù),實現(xiàn)簡單
- 每個連接都需要獨立的線程完成數(shù)據(jù)輸入、業(yè)務(wù)處理、數(shù)據(jù)返回的完整操作
缺點
- 當并發(fā)數(shù)較大時,需要創(chuàng)建大量線程來處理連接,系統(tǒng)資源占用較大
- 連接建立后,如果當前線程暫時沒有數(shù)據(jù)可讀,則線程就阻塞在 read 操作上,造成線程資源浪費,CPU 的利用率非常低,因為線程會被阻塞很長時間,等待 I/O 操作的完成,如果線程阻塞過多甚至?xí)?dǎo)致系統(tǒng)崩潰
Reactor Model
針對傳統(tǒng)阻塞 I/O 模式的 2 個缺點,比較常見的有如下解決方案:
- 基于「I/O 多路復(fù)用」模型,多個連接共用一個阻塞對象,應(yīng)用程序只需要在一個阻塞對象上等待,無需阻塞等待所有連接。當某條連接有新的數(shù)據(jù)可以處理時,操作系統(tǒng)通知應(yīng)用程序,線程從阻塞狀態(tài)返回,開始進行業(yè)務(wù)處理
- 基于「線程池」復(fù)用線程資源,不必再為每個連接創(chuàng)建線程,將連接完成后的具體處理任務(wù)分配給線程進行處理,一個線程可以處理多個連接的業(yè)務(wù)
I/O 多路復(fù)用 + 線程池,這就是 Reactor 模式基本設(shè)計思想
Reactor 模式中主要有 2 個關(guān)鍵組成:
- Reactor:負責(zé)監(jiān)聽和分發(fā)事件,分發(fā)給適當?shù)?Handler 來對 IO 事件做出反應(yīng)
- Handler:處理程序執(zhí)行 I/O 事件要完成的實際任務(wù)
根據(jù)Reactor的數(shù)量和處理資源池線程的數(shù)量不同,有 3 種典型的實現(xiàn):
- 單 Reactor 單線程:Single Reactor Single Thread (SRST) pattern
- 單 Reactor 多線程:Single Reactor Multi-Thread (SRMT) pattern
- 主從 Reactor 多線程:Master-Slave Reactor Multi-Thread (MSRMT) pattern
單 Reactor 單線程
方案說明
- 一個進程里有 Reactor、Acceptor、Handler 這三個對象:
- Reactor 對象的作用是監(jiān)聽和分發(fā)事件;
- Acceptor 對象的作用是獲取連接;
- Handler 對象的作用是處理業(yè)務(wù);
- Reactor 對象通過 select 監(jiān)控客戶端請求事件,收到事件后通過 dispatch 進行分發(fā),具體分發(fā)給 Acceptor 對象還是 Handler 對象,還要看收到的事件類型;
- 如果是建立連接請求事件,則由Acceptor通過accept處理連接請求,然后創(chuàng)建一個Handler對象處理連接完成后的后續(xù)業(yè)務(wù)處理
- 如果不是建立連接事件,則 Reactor 會分發(fā)調(diào)用連接對應(yīng)的Handler對象來響應(yīng)
- Handler 對象通過 read -> 業(yè)務(wù)處理 -> send 的流程來完成完整的業(yè)務(wù)流程
單 Reactor 單線程原理示例圖
特點
模型簡單,所有的 I/O 操作都在一個線程中被執(zhí)行,Reactor 監(jiān)聽所有的 IO 事件,當有事件發(fā)生時,Reactor 會調(diào)用對應(yīng)的 Handler 來處理事件。沒有多線程(全部工作都在同一個線程內(nèi)完成)、線程/進程通信、競爭的問題
缺點
- 性能問題:只有一個線程,無法完全發(fā)揮多核 CPU 的性能。Handler 對象在處理某個連接上的業(yè)務(wù)時,整個進程無法處理其他連接事件,很容易導(dǎo)致性能瓶頸
- 可靠性問題:線程意外跑飛,或者進入死循環(huán),會導(dǎo)致整個系統(tǒng)通信模塊不可用,不能接收和處理外部消息,造成節(jié)點故障
適用場景
適用于并發(fā)需求不高、處理邏輯簡單的情況,比如處理時間不敏感的任務(wù)
示例代碼
下面是一個簡單的示例代碼,實現(xiàn)了單 Reactor 單線程模型
單 Reactor 多線程
方案說明
和單 Reactor 單線程模型的區(qū)別就在于,
- Handler 只負責(zé)響應(yīng)事件,不做具體業(yè)務(wù)處理,通過 read 讀取數(shù)據(jù)后,會分發(fā)給后面的 Worker 線程池進行業(yè)務(wù)處理
- Worker 線程池會分配獨立的線程完成真正的業(yè)務(wù)處理,然后將響應(yīng)結(jié)果發(fā)給 Handler 進行處理
- Handler 收到響應(yīng)結(jié)果后返回給 client
單 Reactor 多線程原理示例圖
特點
單 Reactor 多線程模型中,Reactor 仍然只有一個,但是有多個線程用于處理 I/O 事件。當有事件發(fā)生時,Reactor 會將事件分發(fā)給空閑的線程處理,可以利用多核 CPU 實現(xiàn)更好的并發(fā)性能。
缺點
- 多線程數(shù)據(jù)共享和訪問比較復(fù)雜
- Reactor 承擔(dān)所有事件的監(jiān)聽和響應(yīng),在單線程中運行,高并發(fā)場景下容易成為性能瓶頸
適用場景
單 Reactor 多線程模型適用于并發(fā)需求較高、但是任務(wù)處理邏輯簡單的情況,可以利用多核 CPU 提高并發(fā)性能
示例代碼
在下述代碼中,Reactor 類代表 Reactor 線程,它通過 Selector(Java NIO 包中的類) 監(jiān)聽和分發(fā)事件。Acceptor 類代表事件處理器,它處理連接請求事件,并創(chuàng)建新的 Handler 對象處理讀寫事件。Handler 類代表具體的業(yè)務(wù)邏輯處理器,它處理讀寫事件并返回響應(yīng)。
在 Reactor 類的 run 方法中,通過調(diào)用 Selector 的 select 方法等待事件的發(fā)生,并獲取已經(jīng)就緒的事件。然后遍歷事件集合,將每個事件分發(fā)給一個 Worker 線程處理。Worker 線程采用線程池來實現(xiàn)。
主從 Reactor 多線程
方案說明
針對單 Reactor 多線程模型中,Reactor 在單線程中運行,高并發(fā)場景下容易成為性能瓶頸,主從 Reactor 模型可以讓 Reactor 在多線程中運行:
- Reactor 主線程(MainReactor)通過 select 監(jiān)控建立連接事件,收到事件后通過 Acceptor 接收,處理建立連接事件
- Accepto 處理建立連接事件后,MainReactor 將連接分配給 Reactor 子線程(SubReactor)進行處理
- SubReactor 將連接加入 select 進行監(jiān)聽,并創(chuàng)建一個 Handler 用于處理該連接的響應(yīng)事件
- 當有新的事件發(fā)生時,SubReactor 會調(diào)用連接對應(yīng)的 Handler 進行響應(yīng)
- Handler 通過 read 讀取數(shù)據(jù)后,會分發(fā)給后面的 Worker 線程池進行業(yè)務(wù)處理
- Worker 線程池會分配獨立的線程完成真正的業(yè)務(wù)處理,如何將響應(yīng)結(jié)果發(fā)給 Handler 進行處理
- Handler 收到響應(yīng)結(jié)果后通過 send 將響應(yīng)結(jié)果返回給 Client
主從 Reactor 多線程原理示意圖
特點
- Reactor 父線程與子線程的數(shù)據(jù)交互簡單職責(zé)明確,Reactor 父線程只需要接收新連接,Reactor 子線程完成后續(xù)的業(yè)務(wù)處理
- 父線程與子線程的數(shù)據(jù)交互簡單,Reactor 主線程只需要把新連接傳給子線程,子線程無需返回數(shù)據(jù)
缺點
- 實現(xiàn)較為復(fù)雜:主從 Reactor 模式需要同時處理多個 Reactor 線程之間的協(xié)作和同步,實現(xiàn)起來比較復(fù)雜,需要處理更多的細節(jié)和邊界情況。
- 可能存在的性能瓶頸:在主從 Reactor 模式中,主 Reactor 線程和從 Reactor 線程之間需要通過隊列等方式傳遞事件,可能會存在性能瓶頸。
- 調(diào)試和維護困難:主從 Reactor 模式中包含多個線程和多個 Reactor 對象,調(diào)試和維護都比較困難,需要付出更多的時間和精力。
適用場景
主從 Reactor 多線程模型適用于并發(fā)需求非常高,任務(wù)處理邏輯復(fù)雜,可以通過多個從 Reactor 和線程池來提高并發(fā)性能。
示例代碼
在下述示例代碼中,主 Reactor 線程負責(zé)監(jiān)聽連接事件(SelectionKey.OP_ACCEPT?),從 Reactor 線程負責(zé)監(jiān)聽讀事件(SelectionKey.OP_READ?)和寫事件(SelectionKey.OP_WRITE)。當有新連接到達時,主 Reactor 線程會創(chuàng)建一個新的 Handler 對象,并將其注冊到從 Reactor 線程的 Selector 上。在 Handler 中,讀和寫事件會被提交給線程池
Proactor Model
由于 Reactor 是基于 I/O 多路復(fù)用的,所以 Reactor 其實還是同步的。
而 Proactor 是「異步」的,把 I/O 操作改為異步來進一步提升性能
在 Proactor 線程模型中,應(yīng)用程序通過調(diào)用異步 I/O 操作向系統(tǒng)內(nèi)核發(fā)起 I/O 請求,內(nèi)核將請求放入 I/O 隊列中,并向應(yīng)用程序返回一個 I/O 請求標識符。應(yīng)用程序可以繼續(xù)執(zhí)行其他任務(wù),不必等待 I/O 操作完成。當內(nèi)核完成 I/O 操作時,會向應(yīng)用程序發(fā)送一個通知,并將操作結(jié)果放入 I/O 完成隊列中。應(yīng)用程序可以通過輪詢或者回調(diào)方式獲取完成隊列中的操作結(jié)果,并進行后續(xù)的處理。
Reactor 和 Proactor 的區(qū)別:
- Reactor 是同步網(wǎng)絡(luò)模式,感知的是【就緒可讀寫事件】。在每次感知到有事件發(fā)生(比如可讀就緒事件)后,就需要應(yīng)用進程主動調(diào)用 read 方法來完成數(shù)據(jù)的讀取,也就是要應(yīng)用進程主動將 socket 接收緩存中的數(shù)據(jù)讀到應(yīng)用進程內(nèi)存中,這個過程是同步的,讀取完數(shù)據(jù)后應(yīng)用進程才能處理數(shù)據(jù)。
- Proactor 是異步網(wǎng)絡(luò)模式, 感知的是【已完成的讀寫事件】。在發(fā)起異步讀寫請求時,需要傳入數(shù)據(jù)緩沖區(qū)的地址(用來存放結(jié)果數(shù)據(jù))等信息,這樣系統(tǒng)內(nèi)核才可以自動幫我們把數(shù)據(jù)的讀寫工作完成,這里的讀寫工作全程由操作系統(tǒng)來做,并不需要像 Reactor 那樣還需要應(yīng)用進程主動發(fā)起 read/write 來讀寫數(shù)據(jù),內(nèi)核完成讀寫工作后,就會通知應(yīng)用進程直接處理數(shù)據(jù)。
因此,Reactor 可以理解為「來了事件操作系統(tǒng)通知應(yīng)用進程,讓應(yīng)用進程來處理」,而 Proactor 可以理解為「來了事件操作系統(tǒng)來處理,處理完再通知應(yīng)用進程」
理論上 Proactor 比 Reactor 效率更高,但是有如下缺點:
- 編程復(fù)雜性:由于異步操作流程的事件的初始化和事件完成在時間和空間上都是相互分離的,因此開發(fā)異步應(yīng)用程序更加復(fù)雜。應(yīng)用程序還可能因為反向的流控而變得更加難以Debug
- 內(nèi)存使用:緩沖區(qū)在讀或?qū)懖僮鞯臅r間段內(nèi)必須保持住,可能造成持續(xù)的不確定性,并且每個并發(fā)操作都要求有獨立的緩存,相比 Reactor 模式,在 Socket 已經(jīng)準備好讀或?qū)懬?,是不要求開辟緩存的
- 操作系統(tǒng)支持:Windows 下通過 IOCP 實現(xiàn)了真正的異步 I/O,而在 Linux 系統(tǒng)下,Linux2.6 才引入,目前異步 I/O 還不完善
網(wǎng)站名稱:B站二面被掛,線程模型還不夠熟練?
URL地址:http://fisionsoft.com.cn/article/copodoi.html


咨詢
建站咨詢
