www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 嵌入式客棧
[導(dǎo)讀]堅(jiān)持思考,就會(huì)很酷在Linux系統(tǒng)之中有一個(gè)核心武器:epoll池,在高并發(fā)的,高吞吐的IO系統(tǒng)中常常見(jiàn)到epoll的身影。IO多路復(fù)用在Go里最核心的是Goroutine,也就是所謂的協(xié)程,協(xié)程最妙的一個(gè)實(shí)現(xiàn)就是異步的代碼長(zhǎng)的跟同步代碼一樣。比如在Go中,網(wǎng)絡(luò)IO的read,w...

堅(jiān)持思考,就會(huì)很酷


在 Linux 系統(tǒng)之中有一個(gè)核心武器:epoll 池,在高并發(fā)的,高吞吐的 IO 系統(tǒng)中常常見(jiàn)到 epoll 的身影。


IO 多路復(fù)用


在 Go 里最核心的是 Goroutine ,也就是所謂的協(xié)程,協(xié)程最妙的一個(gè)實(shí)現(xiàn)就是異步的代碼長(zhǎng)的跟同步代碼一樣。比如在 Go 中,網(wǎng)絡(luò) IO 的 read,write 看似都是同步代碼,其實(shí)底下都是異步調(diào)用,一般流程是:

write?(?/*?IO?參數(shù)?*/?)
????請(qǐng)求入隊(duì)
????等待完成
????
?后臺(tái)?loop?程序
????發(fā)送網(wǎng)絡(luò)請(qǐng)求
????喚醒業(yè)務(wù)方
Go 配合協(xié)程在網(wǎng)絡(luò) IO 上實(shí)現(xiàn)了異步流程的代碼同步。核心就是用 epoll 池來(lái)管理網(wǎng)絡(luò) fd 。

實(shí)現(xiàn)形式上,后臺(tái)的程序只需要 1 個(gè)就可以負(fù)責(zé)管理多個(gè) fd 句柄,負(fù)責(zé)應(yīng)對(duì)所有的業(yè)務(wù)方的 IO 請(qǐng)求。這種一對(duì)多的 IO 模式我們就叫做 IO 多路復(fù)用。

多路是指?多個(gè)業(yè)務(wù)方(句柄)并發(fā)下來(lái)的 IO 。

復(fù)用是指?復(fù)用這一個(gè)后臺(tái)處理程序。

站在 IO 系統(tǒng)設(shè)計(jì)人員的角度,業(yè)務(wù)方咱們沒(méi)辦法提要求,因?yàn)闃I(yè)務(wù)是上帝,只有你服從的份,他們要?jiǎng)?chuàng)建多個(gè) fd,那么你就需要負(fù)責(zé)這些 fd 的處理,并且最好還要并發(fā)起來(lái)。

業(yè)務(wù)方?jīng)]法提要求,那么只能要求后臺(tái) loop 程序了!

要求什么呢?快!快!快!這就是最核心的要求,處理一定要快,要給每一個(gè) fd 通道最快的感受,要讓每一個(gè) fd 覺(jué)得,你只在給他一個(gè)人跑腿。

那有人又問(wèn)了,那我一個(gè) IO 請(qǐng)求(比如 write )對(duì)應(yīng)一個(gè)線程來(lái)處理,這樣所有的 IO 不都并發(fā)了嗎?是可以,但是有瓶頸,線程數(shù)一旦多了,性能是反倒會(huì)差的。

這里不再對(duì)比多線程和 IO 多路復(fù)用實(shí)現(xiàn)高并發(fā)之間的區(qū)別,詳細(xì)的可以去了解下 nginx 和 redis 高并發(fā)的秘密。


?1???最樸實(shí)的實(shí)現(xiàn)方式?

我不用任何其他系統(tǒng)調(diào)用,能否實(shí)現(xiàn) IO 多路復(fù)用?

可以的。那么寫個(gè) for 循環(huán),每次都嘗試 IO 一下,讀/寫到了就處理,讀/寫不到就 sleep 下。這樣我們不就實(shí)現(xiàn)了 1 對(duì)多的 IO 多路復(fù)用嘛。

while?True:
????for?each?句柄數(shù)組?{
????????read/write(fd,?/*?參數(shù)?*/)
????}
????sleep(1s)
慢著,有個(gè)問(wèn)題,上面的程序可能會(huì)被卡死在第三行,使得整個(gè)系統(tǒng)不得運(yùn)行,為什么?

默認(rèn)情況下,我們?create 出的句柄是阻塞類型的。我們讀數(shù)據(jù)的時(shí)候,如果數(shù)據(jù)還沒(méi)準(zhǔn)備好,是會(huì)需要等待的,當(dāng)我們寫數(shù)據(jù)的時(shí)候,如果還沒(méi)準(zhǔn)備好,默認(rèn)也會(huì)卡住等待。所以,在上面?zhèn)未a第三行是可能被直接卡死,而導(dǎo)致整個(gè)線程都得到不到運(yùn)行。

舉個(gè)例子,現(xiàn)在有 11,12,13 這 3 個(gè)句柄,現(xiàn)在 11 讀寫都沒(méi)有準(zhǔn)備好,只要 read/write(11, /*參數(shù)*/) 就會(huì)被卡住,但 12,13 這兩個(gè)句柄都準(zhǔn)備好了,那遍歷句柄數(shù)組 11,12,13 的時(shí)候就會(huì)卡死在前面,后面 12,13 則得不到運(yùn)行。這不符合我們的預(yù)期,因?yàn)槲覀?IO 多路復(fù)用的 loop 線程是公共服務(wù),不能因?yàn)橐粋€(gè) fd 就直接癱瘓。

那這個(gè)問(wèn)題怎么解決?

只需要把 fd 都設(shè)置成非阻塞模式。這樣 read/write 的時(shí)候,如果數(shù)據(jù)沒(méi)準(zhǔn)備好,返回 EAGIN 的錯(cuò)誤即可,不會(huì)卡住線程,從而整個(gè)系統(tǒng)就運(yùn)轉(zhuǎn)起來(lái)了。比如上面句柄 11 還未就緒,那么 read/write(11, /*參數(shù)*/) 不會(huì)阻塞,只會(huì)報(bào)個(gè) EAGIN 的錯(cuò)誤,這種錯(cuò)誤需要特殊處理,然后 loop 線程可以繼續(xù)執(zhí)行 12,13 的讀寫。

以上就是最樸實(shí)的 IO 多路復(fù)用的實(shí)現(xiàn)了。但好像在生產(chǎn)環(huán)境沒(méi)見(jiàn)過(guò)這種 IO 多路復(fù)用的實(shí)現(xiàn)?為什么?

因?yàn)檫€不夠高級(jí)。for 循環(huán)每次要定期 sleep 1s,這個(gè)會(huì)導(dǎo)致吞吐能力極差,因?yàn)楹芸赡茉趧偤靡?sleep 的時(shí)候,所有的 fd 都準(zhǔn)備好 IO 數(shù)據(jù),而這個(gè)時(shí)候卻要硬生生的等待 1s,可想而知。。。

那有同學(xué)又要質(zhì)疑了,那 for 循環(huán)里面就不 sleep 嘛,這樣不就能及時(shí)處理了嗎?

及時(shí)是及時(shí)了,但是 CPU 估計(jì)要跑飛了。不加 sleep ,那在沒(méi)有 fd 需要處理的時(shí)候,估計(jì) CPU 都要跑到 100% 了。這個(gè)也是無(wú)法接受的。

糾結(jié)了,那 sleep 吞吐不行,不 sleep 浪費(fèi) cpu,怎么辦?

這種情況用戶態(tài)很難有所作為,只能求助內(nèi)核來(lái)提供機(jī)制協(xié)助來(lái)。因?yàn)閮?nèi)核才能及時(shí)的管理這些事件的通知和調(diào)度。

我們?cè)偈崂硐?IO 多路復(fù)用的需求和原理。IO 多路復(fù)用就是 1 個(gè)線程處理 多個(gè) fd 的模式。我們的要求是:這個(gè) “1” 就要盡可能的快,避免一切無(wú)效工作,要把所有的時(shí)間都用在處理句柄的 IO 上,不能有任何空轉(zhuǎn),sleep 的時(shí)間浪費(fèi)。

有沒(méi)有一種工具,我們把一籮筐的 fd 放到里面,只要有一個(gè) fd 能夠讀寫數(shù)據(jù),后臺(tái) loop 線程就要立馬喚醒,全部馬力跑起來(lái)。其他時(shí)間要把 cpu 讓出去。

能做到嗎?能,但這種需求只能內(nèi)核提供機(jī)制滿足你。


?2???這事 Linux 內(nèi)核必須要給個(gè)說(shuō)法?

是的,想要不用 sleep 這種辣眼睛的實(shí)現(xiàn),Linux 內(nèi)核必須出手了,畢竟 IO 的處理都是內(nèi)核之中,數(shù)據(jù)好沒(méi)好內(nèi)核最清楚。

內(nèi)核一口氣提供了 3 種工具 selectpoll,epoll 。

為什么有 3 種?

歷史不斷改進(jìn),矬 -> 較矬 -> 臥槽、高效 的演變而已。



Linux 還有其他方式可以實(shí)現(xiàn) IO 多路復(fù)用嗎?

好像沒(méi)有了!

這 3 種到底是做啥的?

這 3 種都能夠管理 fd 的可讀可寫事件,在所有 fd 不可讀不可寫無(wú)所事事的時(shí)候,可以阻塞線程,切走 cpu 。fd 有情況的時(shí)候,都要線程能夠要能被喚醒。

而這三種方式以 epoll 池的效率最高。為什么效率最高?

其實(shí)很簡(jiǎn)單,這里不詳說(shuō),其實(shí)無(wú)非就是 epoll 做的無(wú)用功最少,select 和 poll 或多或少都要多余的拷貝,盲猜(遍歷才知道)fd ,所以效率自然就低了。

舉個(gè)例子,以 select 和 epoll 來(lái)對(duì)比舉例,池子里管理了 1024 個(gè)句柄,loop 線程被喚醒的時(shí)候,select 都是蒙的,都不知道這 1024 個(gè) fd 里誰(shuí) IO 準(zhǔn)備好了。這種情況怎么辦?只能遍歷這 1024 個(gè) fd ,一個(gè)個(gè)測(cè)試。假如只有一個(gè)句柄準(zhǔn)備好了,那相當(dāng)于做了 1 千多倍的無(wú)效功。

epoll 則不同,從 epoll_wait 醒來(lái)的時(shí)候就能精確的拿到就緒的 fd 數(shù)組,不需要任何測(cè)試,拿到的就是要處理的。


epoll 池原理


下面我們看一下 epoll 池的使用和原理。


?1???epoll 涉及的系統(tǒng)調(diào)用

epoll 的使用非常簡(jiǎn)單,只有下面 3 個(gè)系統(tǒng)調(diào)用。

epoll_create
epollctl
epollwait
就這?是的,就這么簡(jiǎn)單。

  • epollcreate 負(fù)責(zé)創(chuàng)建一個(gè)池子,一個(gè)監(jiān)控和管理句柄 fd 的池子;
  • epollctl 負(fù)責(zé)管理這個(gè)池子里的 fd 增、刪、改;
  • epollwait 就是負(fù)責(zé)打盹的,讓出 CPU 調(diào)度,但是只要有“事”,立馬會(huì)從這里喚醒;

?2???epoll 高效的原理

Linux 下,epoll 一直被吹爆,作為高并發(fā) IO 實(shí)現(xiàn)的秘密武器。其中原理其實(shí)非常樸實(shí):epoll 的實(shí)現(xiàn)幾乎沒(méi)有做任何無(wú)效功。 我們從使用的角度切入來(lái)一步步分析下。

首先,epoll 的第一步是創(chuàng)建一個(gè)池子。這個(gè)使用 epoll_create 來(lái)做:

原型:

int?epoll_create(int?size);
示例:

epollfd?=?epoll_create(1024);
if?(epollfd?==?-1)?{
????perror("epoll_create");
????exit(EXIT_FAILURE);
}
這個(gè)池子對(duì)我們來(lái)說(shuō)是黑盒,這個(gè)黑盒是用來(lái)裝 fd 的,我們暫不糾結(jié)其中細(xì)節(jié)。我們拿到了一個(gè) epollfd ,這個(gè) epollfd 就能唯一代表這個(gè) epoll 池。注意,這里又有一個(gè)細(xì)節(jié):用戶可以創(chuàng)建多個(gè) epoll 池。

然后,我們就要往這個(gè) epoll 池里放 fd 了,這就要用到 epoll_ctl

原型:

int?epoll_ctl(int?epfd,?int?op,?int?fd,?struct?epoll_event?*event);
示例:

if?(epoll_ctl(epollfd,?EPOLL_CTL_ADD,?11,?
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉