原創(chuàng)聲明
作者: 劉丹冰Aceld
本文主要介紹常見的Server的并發(fā)模型,這些模型與編程語言本身無關(guān)费就,有的編程語言可能在語法上直接透明了模型本質(zhì)症概,所以開發(fā)者沒必要一定要基于模型去編寫幔荒,只是需要知道和了解并發(fā)模型的構(gòu)成和特點(diǎn)即可。
那么在了解并發(fā)模型之前邢锯,我們需要兩個必備的前置知識:
- socket網(wǎng)絡(luò)編程
- 多路IO復(fù)用機(jī)制
- 多線程/多進(jìn)程等并發(fā)編程理論
模型一、單線程Accept(無IO復(fù)用)
(1) 模型結(jié)構(gòu)圖
(2) 模型分析
① 主線程main thread
執(zhí)行阻塞Accept搀别,每次客戶端Connect鏈接過來丹擎,main thread
中accept響應(yīng)并建立連接
② 創(chuàng)建鏈接成功,得到Connfd1
套接字后, 依然在main thread
串行處理套接字讀寫歇父,并處理業(yè)務(wù)蒂培。
③ 在②處理業(yè)務(wù)中,如果有新客戶端Connect
過來榜苫,Server
無響應(yīng)护戳,直到當(dāng)前套接字全部業(yè)務(wù)處理完畢。
④ 當(dāng)前客戶端處理完后垂睬,完畢鏈接媳荒,處理下一個客戶端請求。
(3) 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- socket編程流程清晰且簡單驹饺,適合學(xué)習(xí)使用钳枕,了解socket基本編程流程。
缺點(diǎn):
該模型并非并發(fā)模型赏壹,是串行的服務(wù)器鱼炒,同一時刻,監(jiān)聽并響應(yīng)最大的網(wǎng)絡(luò)請求量為
1
蝌借。 即并發(fā)量為1
昔瞧。僅適合學(xué)習(xí)基本socket編程,不適合任何服務(wù)器Server構(gòu)建菩佑。
模型二自晰、單線程Accept+多線程讀寫業(yè)務(wù)(無IO復(fù)用)
(1) 模型結(jié)構(gòu)圖
(2) 模型分析
① 主線程main thread
執(zhí)行阻塞Accept,每次客戶端Connect鏈接過來擎鸠,main thread
中accept響應(yīng)并建立連接
② 創(chuàng)建鏈接成功,得到Connfd1
套接字后缘圈,創(chuàng)建一個新線程thread1
用來處理客戶端的讀寫業(yè)務(wù)劣光。main thead
依然回到Accept
阻塞等待新客戶端。
③ thread1
通過套接字Connfd1
與客戶端進(jìn)行通信讀寫糟把。
④ server在②處理業(yè)務(wù)中绢涡,如果有新客戶端Connect
過來,main thread
中Accept
依然響應(yīng)并建立連接遣疯,重復(fù)②過程雄可。
(3) 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 基于
模型一:單線程Accept(無IO復(fù)用)
支持了并發(fā)的特性。 - 使用靈活,一個客戶端對應(yīng)一個線程單獨(dú)處理数苫,
server
處理業(yè)務(wù)內(nèi)聚程度高聪舒,客戶端無論如何寫,服務(wù)端均會有一個線程做資源響應(yīng)虐急。
缺點(diǎn):
- 隨著客戶端的數(shù)量增多箱残,需要開辟的線程也增加,客戶端與server線程數(shù)量
1:1
正比關(guān)系止吁,一次對于高并發(fā)場景被辑,線程數(shù)量收到硬件上限瓶頸。 - 對于長鏈接敬惦,客戶端一旦無業(yè)務(wù)讀寫盼理,只要不關(guān)閉,server的對應(yīng)線程依然需要保持連接(心跳俄删、健康監(jiān)測等機(jī)制)夏漱,占用連接資源和線程開銷資源浪費(fèi)。
- 僅適合客戶端數(shù)量不大隆判,并且數(shù)量可控的場景使用亦鳞。
僅適合學(xué)習(xí)基本socket編程,不適合任何服務(wù)器Server構(gòu)建迅矛。
模型三妨猩、單線程多路IO復(fù)用
(1) 模型結(jié)構(gòu)圖
(2) 模型分析
① 主線程main thread
創(chuàng)建listenFd
之后,采用多路I/O復(fù)用機(jī)制(如:select秽褒、epoll)進(jìn)行IO狀態(tài)阻塞監(jiān)控壶硅。有Client1
客戶端Connect
請求,I/O復(fù)用機(jī)制檢測到ListenFd
觸發(fā)讀事件销斟,則進(jìn)行Accept
建立連接庐椒,并將新生成的connFd1
加入到監(jiān)聽I/O集合
中。
② Client1
再次進(jìn)行正常讀寫業(yè)務(wù)請求蚂踊,main thread
的多路I/O復(fù)用機(jī)制
阻塞返回约谈,會觸該套接字的讀/寫事件等。
③ 對于Client1
的讀寫業(yè)務(wù)犁钟,Server依然在main thread
執(zhí)行流程提繼續(xù)執(zhí)行棱诱,此時如果有新的客戶端Connect
鏈接請求過來,Server將沒有即時響應(yīng)涝动。
④ 等到Server處理完一個連接的Read+Write
操作迈勋,繼續(xù)回到多路I/O復(fù)用機(jī)制
阻塞,其他鏈接過來重復(fù) ②醋粟、③流程靡菇。
(3) 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 單流程解決了可以同時監(jiān)聽多個客戶端讀寫狀態(tài)的模型重归,不需要
1:1
與客戶端的線程數(shù)量關(guān)系。 - 多路I/O復(fù)用阻塞厦凤,非忙詢狀態(tài)鼻吮,不浪費(fèi)CPU資源, CPU利用率較高泳唠。
缺點(diǎn):
- 雖然可以監(jiān)聽多個客戶端的讀寫狀態(tài)狈网,但是同一時間內(nèi),只能處理一個客戶端的讀寫操作笨腥,實際上讀寫的業(yè)務(wù)并發(fā)為1拓哺。
- 多客戶端訪問Server,業(yè)務(wù)為串行執(zhí)行脖母,大量請求會有排隊延遲現(xiàn)象士鸥,如圖中⑤所示,當(dāng)
Client3
占據(jù)main thread
流程時谆级,Client1,Client2
流程卡在IO復(fù)用
等待下次監(jiān)聽觸發(fā)事件烤礁。
模型四、單線程多路IO復(fù)用+多線程讀寫業(yè)務(wù)(業(yè)務(wù)工作池)
(1) 模型結(jié)構(gòu)圖
(2) 模型分析
① 主線程main thread
創(chuàng)建listenFd
之后肥照,采用多路I/O復(fù)用機(jī)制(如:select脚仔、epoll)進(jìn)行IO狀態(tài)阻塞監(jiān)控。有Client1
客戶端Connect
請求舆绎,I/O復(fù)用機(jī)制檢測到ListenFd
觸發(fā)讀事件鲤脏,則進(jìn)行Accept
建立連接,并將新生成的connFd1
加入到監(jiān)聽I/O集合
中吕朵。
② 當(dāng)connFd1
有可讀消息猎醇,觸發(fā)讀事件,并且進(jìn)行讀寫消息
③ main thread
按照固定的協(xié)議讀取消息努溃,并且交給worker pool
工作線程池硫嘶, 工作線程池在server啟動之前就已經(jīng)開啟固定數(shù)量的thread
,里面的線程只處理消息業(yè)務(wù)梧税,不進(jìn)行套接字讀寫操作沦疾。
④ 工作池處理完業(yè)務(wù),觸發(fā)connFd1
寫事件第队,將回執(zhí)客戶端的消息通過main thead
寫給對方哮塞。
(3) 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 對于
模型三
, 將業(yè)務(wù)處理部分,通過工作池分離出來斥铺,減少多客戶端訪問Server彻桃,業(yè)務(wù)為串行執(zhí)行坛善,大量請求會有排隊延遲時間晾蜘。 - 實際上讀寫的業(yè)務(wù)并發(fā)為1邻眷,但是業(yè)務(wù)流程并發(fā)為worker pool線程數(shù)量,加快了業(yè)務(wù)處理并行效率剔交。
缺點(diǎn):
- 讀寫依然為
main thread
單獨(dú)處理肆饶,最高讀寫并行通道依然為1. - 雖然多個worker線程處理業(yè)務(wù),但是最后返回給客戶端岖常,依舊需要排隊驯镊,因為出口還是
main thread
的Read + Write
模型五、單線程IO復(fù)用+多線程IO復(fù)用(鏈接線程池)
(1) 模型結(jié)構(gòu)圖
(2) 模型分析
① Server在啟動監(jiān)聽之前竭鞍,開辟固定數(shù)量(N)的線程板惑,用Thead Pool
線程池管理
② 主線程main thread
創(chuàng)建listenFd
之后,采用多路I/O復(fù)用機(jī)制(如:select偎快、epoll)進(jìn)行IO狀態(tài)阻塞監(jiān)控冯乘。有Client1
客戶端Connect
請求,I/O復(fù)用機(jī)制檢測到ListenFd
觸發(fā)讀事件晒夹,則進(jìn)行Accept
建立連接裆馒,并將新生成的connFd1
分發(fā)給Thread Pool
中的某個線程進(jìn)行監(jiān)聽。
③ Thread Pool
中的每個thread
都啟動多路I/O復(fù)用機(jī)制(select丐怯、epoll)
,用來監(jiān)聽main thread
建立成功并且分發(fā)下來的socket套接字喷好。
④ 如圖, thread
監(jiān)聽ConnFd1读跷、ConnFd2
, thread2
監(jiān)聽ConnFd3
,thread3
監(jiān)聽ConnFd4
. 當(dāng)對應(yīng)的ConnFd
有讀寫事件梗搅,對應(yīng)的線程處理該套接字的讀寫及業(yè)務(wù)。
(3) 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 將
main thread
的單流程讀寫舔亭,分散到多線程完成些膨,這樣增加了同一時刻的讀寫并行通道,并行通道數(shù)量N
钦铺,N
為線程池Thread
數(shù)量订雾。 - server同時監(jiān)聽的
ConnFd套接字
數(shù)量幾乎成倍增大,之前的全部監(jiān)控數(shù)量取決于main thread
的多路I/O復(fù)用機(jī)制
的最大限制(select 默認(rèn)為1024矛洞, epoll默認(rèn)與內(nèi)存大小相關(guān)洼哎,約3~6w不等),所以理論單點(diǎn)Server最高響應(yīng)并發(fā)數(shù)量為N*(3~6W)
(N
為線程池Thread
數(shù)量沼本,建議與CPU核心成比例1:1)噩峦。 - 如果良好的線程池數(shù)量和CPU核心數(shù)適配,那么可以嘗試CPU核心與Thread進(jìn)行綁定抽兆,從而降低CPU的切換頻率识补,提升每個
Thread
處理合理業(yè)務(wù)的效率,降低CPU切換成本開銷辫红。
缺點(diǎn):
- 雖然監(jiān)聽的并發(fā)數(shù)量提升凭涂,但是最高讀寫并行通道依然為
N
祝辣,而且多個身處同一個Thread的客戶端,會出現(xiàn)讀寫延遲現(xiàn)象切油,實際上每個Thread
的模型特征與模型三:單線程多路IO復(fù)用
一致蝙斜。
模型五(進(jìn)程版)、單進(jìn)程多路I/O復(fù)用+多進(jìn)程多路I/O復(fù)用(進(jìn)程池)
(1) 模型結(jié)構(gòu)圖
(2) 模型分析
與五澎胡、單線程IO復(fù)用+多線程IO復(fù)用(鏈接線程池)
無大差異孕荠。
不同處
- 進(jìn)程和線程的內(nèi)存布局不同導(dǎo)致,
main process
(主進(jìn)程)不再進(jìn)行Accept
操作攻谁,而是將Accept
過程分散到各個子進(jìn)程(process)
中. - 進(jìn)程的特性稚伍,資源獨(dú)立,所以
main process
如果Accept成功的fd戚宦,其他進(jìn)程無法共享資源槐瑞,所以需要各子進(jìn)程自行Accept創(chuàng)建鏈接 -
main process
只是監(jiān)聽ListenFd
狀態(tài),一旦觸發(fā)讀事件(有新連接請求). 通過一些IPC(進(jìn)程間通信:如信號阁苞、共享內(nèi)存困檩、管道)等, 讓各自子進(jìn)程Process
競爭Accept
完成鏈接建立,并各自監(jiān)聽那槽。
(3) 優(yōu)缺點(diǎn)
與五悼沿、單線程IO復(fù)用+多線程IO復(fù)用(鏈接線程池)
無大差異。
不同處:
多進(jìn)程內(nèi)存資源空間占用稍微大一些
多進(jìn)程模型安全穩(wěn)定型較強(qiáng)骚灸,這也是因為各自進(jìn)程互不干擾的特點(diǎn)導(dǎo)致糟趾。
模型六、單線程多路I/O復(fù)用+多線程多路I/O復(fù)用+多線程
(1) 模型結(jié)構(gòu)圖
(2) 模型分析
① Server在啟動監(jiān)聽之前甚牲,開辟固定數(shù)量(N)的線程义郑,用Thead Pool
線程池管理
② 主線程main thread
創(chuàng)建listenFd
之后,采用多路I/O復(fù)用機(jī)制(如:select丈钙、epoll)進(jìn)行IO狀態(tài)阻塞監(jiān)控非驮。有Client1
客戶端Connect
請求,I/O復(fù)用機(jī)制檢測到ListenFd
觸發(fā)讀事件雏赦,則進(jìn)行Accept
建立連接劫笙,并將新生成的connFd1
分發(fā)給Thread Pool
中的某個線程進(jìn)行監(jiān)聽。
③ Thread Pool
中的每個thread
都啟動多路I/O復(fù)用機(jī)制(select星岗、epoll)
,用來監(jiān)聽main thread
建立成功并且分發(fā)下來的socket套接字填大。一旦其中某個被監(jiān)聽的客戶端套接字觸發(fā)I/O讀寫事件
,那么,會立刻開辟一個新線程來處理I/O讀寫
業(yè)務(wù)俏橘。
④ 但某個讀寫線程完成當(dāng)前讀寫業(yè)務(wù)允华,如果當(dāng)前套接字沒有被關(guān)閉,那么將當(dāng)前客戶端套接字如:ConnFd3
重新加回線程池的監(jiān)控線程中,同時自身線程自我銷毀靴寂。
(3) 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
在
模型五汉额、單線程IO復(fù)用+多線程IO復(fù)用(鏈接線程池)
基礎(chǔ)上,除了能夠保證同時響應(yīng)的最高并發(fā)數(shù)
榨汤,又能解決讀寫并行通道
局限的問題。同一時刻的讀寫并行通道怎茫,達(dá)到
最大化極限
收壕,一個客戶端可以對應(yīng)一個單獨(dú)執(zhí)行流程處理讀寫業(yè)務(wù),讀寫并行通道與客戶端數(shù)量1:1
關(guān)系轨蛤。
缺點(diǎn):
- 該模型過于理想化蜜宪,因為要求CPU核心數(shù)量足夠大。
- 如果硬件CPU數(shù)量可數(shù)(目前的硬件情況)祥山,那么該模型將造成大量的CPU切換成本浪費(fèi)圃验。因為為了保證讀寫并行通道與客戶端
1:1
的關(guān)系,那么Server需要開辟的Thread
數(shù)量就與客戶端一致缝呕,那么線程池中做多路I/O復(fù)用
的監(jiān)聽線程池綁定CPU數(shù)量將變得毫無意義澳窑。 - 如果每個臨時的讀寫
Thread
都能夠綁定一個單獨(dú)的CPU,那么此模型將是最優(yōu)模型供常。但是目前CPU的數(shù)量無法與客戶端的數(shù)量達(dá)到一個量級摊聋,目前甚至差的不是幾個量級的事。
總結(jié)
綜上栈暇,我們整理了7中Server的服務(wù)器處理結(jié)構(gòu)模型麻裁,每個模型都有各自的特點(diǎn)和優(yōu)勢,那么對于多少應(yīng)付高并發(fā)和高CPU利用率的模型源祈,目前多數(shù)采用的是模型五(或模型五進(jìn)程版煎源,如Nginx就是類似模型五進(jìn)程版的改版)。
至于并發(fā)模型并非設(shè)計的約復(fù)雜越好香缺,也不是線程開辟的越多越好手销,我們要考慮硬件的利用與和切換成本的開銷。模型六設(shè)計就極為復(fù)雜图张,線程較多原献,但以當(dāng)今的硬件能力無法支撐,反倒導(dǎo)致該模型性能極差埂淮。所以對于不同的業(yè)務(wù)場景也要選擇適合的模型構(gòu)建姑隅,并不是一定固定就要使用某個來應(yīng)用。
關(guān)于作者:
mail: danbing.at@gmail.com
github: https://github.com/aceld
原創(chuàng)書籍gitbook: https://www.kancloud.cn/@aceld
文章推薦
開源軟件作品
(原創(chuàng)開源)Zinx-基于Golang輕量級服務(wù)器并發(fā)框架-完整版(附教程視頻)
(原創(chuàng)開源)Lars-基于C++負(fù)載均衡遠(yuǎn)程調(diào)度系統(tǒng)-完整版
精選文章
典藏版-Golang調(diào)度器GMP原理與調(diào)度全分析
典藏版-Golang三色標(biāo)記倔撞、混合寫屏障GC模式圖文全分析
最常用的調(diào)試 golang 的 bug 以及性能問題的實踐方法讲仰?