一、基礎(chǔ)知識(shí)
1、一個(gè)主機(jī)的端口號(hào)為所有進(jìn)程所共享,但普通用戶(hù)進(jìn)程綁定bind不了一些特殊端口號(hào)如20、80等。
多個(gè)進(jìn)程不能同時(shí)監(jiān)聽(tīng)listen同一個(gè)端口,會(huì)失敗。當(dāng)然父進(jìn)程可以先listen然后fork多個(gè)子進(jìn)程,多個(gè)子進(jìn)程都可以accept這個(gè)sock,即搶奪式響應(yīng)(驚群效應(yīng))。
關(guān)注4元組是否能唯一確定一個(gè)連接?
2、每個(gè)進(jìn)程都有自己的文件描述符(包括file fd, socket fd, timer fd, event fd, signal fd),一般是1024,可以通過(guò)ulimit -n 設(shè)置,但所有進(jìn)程打開(kāi)的文件描述符總數(shù)有上限,跟主機(jī)的內(nèi)存有關(guān)。
3、一個(gè)進(jìn)程內(nèi)的所有線(xiàn)程共享進(jìn)程的文件描述符。
二、并發(fā)服務(wù)器方案:
1、循環(huán)式/迭代式( iterative )服務(wù)器
無(wú)法充分利用多核CPU,不適合執(zhí)行時(shí)間較長(zhǎng)的服務(wù),即適用于短連接。如果是長(zhǎng)連接則需要在read/write之間循環(huán),那么只能服務(wù)一個(gè)客戶(hù)端。
2、并發(fā)式(concurrent)服務(wù)器
one connection per process/one connection per thread
適合執(zhí)行時(shí)間比較長(zhǎng)的服務(wù)
one connection per process : 主進(jìn)程每次fork 之后要關(guān)閉connfd,子進(jìn)程要關(guān)閉listenfd
one connection per thread : 主線(xiàn)程每次accept 回來(lái)就創(chuàng)建一個(gè)子線(xiàn)程服務(wù),由于線(xiàn)程共享文件描述符,故不用關(guān)閉。
3、prefork or pre threaded(容易發(fā)生“驚群”現(xiàn)象,即多個(gè)子進(jìn)程都處于accept狀態(tài))
4、反應(yīng)式( reactive )服務(wù)器 (reactor模式)(select/poll/epoll)
并發(fā)處理多個(gè)請(qǐng)求,實(shí)際上是在一個(gè)線(xiàn)程中完成。無(wú)法充分利用多核CPU
不適合執(zhí)行時(shí)間比較長(zhǎng)的服務(wù),所以為了讓客戶(hù)感覺(jué)是在“并發(fā)”處理而不是“循環(huán)”處理,每個(gè)請(qǐng)求必須在相對(duì)較短時(shí)間內(nèi)執(zhí)行。
5、reactor + thread per request(過(guò)渡方案)
6、reactor + worker thread(過(guò)渡方案)
7、reactor + thread pool(能適應(yīng)密集計(jì)算)
muduo庫(kù)中的/example/suduku/ 中有這樣一個(gè)例子,因?yàn)閿?shù)獨(dú)求解是計(jì)算密集型任務(wù)。
在實(shí)踐中為了reactor能快速回到事件循環(huán)去響應(yīng)請(qǐng)求,經(jīng)常將讀到的數(shù)據(jù)put到一個(gè)環(huán)形內(nèi)存隊(duì)列(一般內(nèi)存or共享內(nèi)存),而thread pool的線(xiàn)程則從中讀取進(jìn)行數(shù)據(jù)計(jì)算。
需要C/C++ linux服務(wù)器架構(gòu)師學(xué)習(xí)資料私信“資料”(資料包括C/C++,Linux,golang技術(shù),Nginx,ZeroMQ,MySQL,redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK,ffmpeg等),免費(fèi)分享
8、multiple reactors(能適應(yīng)更大的突發(fā)I/O)
reactors in threads(one loop per thread)
reactors in processes
一般來(lái)說(shuō)一個(gè)subReactor適用于一個(gè)千兆網(wǎng)口
9、multiple reactors + thread pool(one loop per thread + threadpool)(突發(fā)I/O與密集計(jì)算)
subReactor可以有多個(gè),但threadpool只有一個(gè)。
10、proactor服務(wù)器(proactor模式,基于異步I/O)
理論上proactor比reactor效率要高一些
異步I/O能夠讓I/O操作與計(jì)算重疊。充分利用DMA特性。
Linux異步IO
glibc aio(aio_*),有bug
kernel native aio(io_*),也不完美。目前僅支持 O_DIRECT 方式來(lái)對(duì)磁盤(pán)讀寫(xiě),跳過(guò)系統(tǒng)緩存。要自已實(shí)現(xiàn)緩存,難度不小。
boost asio實(shí)現(xiàn)的proactor,實(shí)際上不是真正意義上的異步I/O,底層是用epoll來(lái)實(shí)現(xiàn)的,模擬異步I/O的。
常見(jiàn)并發(fā)服務(wù)器方案比較:
三、一些常見(jiàn)問(wèn)題
1、Linux能同時(shí)啟動(dòng)多少個(gè)線(xiàn)程?
對(duì)于 32-bit Linux,一個(gè)進(jìn)程的地址空間是 4G,其中用戶(hù)態(tài)能訪(fǎng)問(wèn) 3G 左右,而一個(gè)線(xiàn)程的默認(rèn)棧 (stack) 大小是 8M,心算可知,一個(gè)進(jìn)程大約最多能同時(shí)啟動(dòng) 350 個(gè)線(xiàn)程左右。
2、多線(xiàn)程能提高并發(fā)度嗎?
如果指的是“并發(fā)連接數(shù)”,不能。
假如單純采用 thread per connection 的模型,那么并發(fā)連接數(shù)大約350,這遠(yuǎn)遠(yuǎn)低于基于事件的單線(xiàn)程程序所能輕松達(dá)到的并發(fā)連接數(shù)(幾千上萬(wàn),甚至幾萬(wàn))。所謂“基于事件”,指的是用 IO multiplexing event loop 的編程模型,又稱(chēng) Reactor 模式。
3、多線(xiàn)程能提高吞吐量嗎?
對(duì)于計(jì)算密集型服務(wù),不能。
如果要在一個(gè)8核的機(jī)器上壓縮100個(gè)1G的文本文件,每個(gè)core的處理能力為200MB/s,那么“每次起8個(gè)進(jìn)程,一個(gè)進(jìn)程壓縮一個(gè)文件”與“只啟動(dòng)一個(gè)進(jìn)程(8個(gè)線(xiàn)程并發(fā)壓縮一個(gè)文件)”,這兩種方式總耗時(shí)相當(dāng),但是第二種方式能較快的拿到第一個(gè)壓縮完的文件。
4、多線(xiàn)程能提高響應(yīng)時(shí)間嗎?
可以。參考問(wèn)題3
5、多線(xiàn)程程序日志庫(kù)要求
線(xiàn)程安全,即多個(gè)線(xiàn)程可以并發(fā)寫(xiě)日志,兩個(gè)線(xiàn)程的日志消息不會(huì)出現(xiàn)交織。
用一個(gè)全局的mutex保護(hù)IO
每個(gè)線(xiàn)程單獨(dú)寫(xiě)一個(gè)日志文件
前者造成全部線(xiàn)程搶占一個(gè)鎖(串行寫(xiě)入)
后者有可能讓業(yè)務(wù)線(xiàn)程阻塞在寫(xiě)磁盤(pán)操作上。(磁盤(pán)IO時(shí)間比較長(zhǎng))
解決辦法:用一個(gè)logging線(xiàn)程負(fù)責(zé)收集日志消息,并寫(xiě)入日志文件,其他業(yè)務(wù)線(xiàn)程只管往這個(gè)“日志線(xiàn)程”發(fā)送日志消息(如通過(guò)BlockingQueue提供接口),這稱(chēng)為“異步日志”,也是一個(gè)經(jīng)典的生產(chǎn)者消費(fèi)者模型。
6、線(xiàn)程池大小的選擇
如果池中執(zhí)行任務(wù)時(shí),密集計(jì)算所占時(shí)間比重為P(0<P<=1),而系統(tǒng)一共有C個(gè)CPU,為了讓C個(gè)CPU跑滿(mǎn)而不過(guò)載,線(xiàn)程池大小的經(jīng)驗(yàn)公式T=C/P,即T*P=C(讓CPU剛好跑滿(mǎn) )
假設(shè)C=8,P=1.0,線(xiàn)程池的任務(wù)完全密集計(jì)算,只要8個(gè)活動(dòng)線(xiàn)程就能讓CPU飽和
假設(shè)C=8,P=0.5,線(xiàn)程池的任務(wù)有一半是計(jì)算,一半是IO,那么T=16,也就是16個(gè)“50%繁忙的線(xiàn)程”能讓8個(gè)CPU忙個(gè)不停。
7、線(xiàn)程分類(lèi)
I/O線(xiàn)程(這里特指網(wǎng)絡(luò)I/O)
計(jì)算線(xiàn)程
第三方庫(kù)所用線(xiàn)程,如logging,又比如database






