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

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

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


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


IO 多路復(fù)用


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

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

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

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

復(fù)用是指?復(fù)用這一個后臺處理程序。

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

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

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

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

這里不再對比多線程和 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ù)用?

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

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

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

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

那這個問題怎么解決?

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

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

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

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

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

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

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

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

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

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


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

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

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

為什么有 3 種?

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



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

好像沒有了!

這 3 種到底是做啥的?

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

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

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

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

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


epoll 池原理


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


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

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

epoll_create
epollctl
epollwait
就這?是的,就這么簡單。

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

?2???epoll 高效的原理

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

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

原型:

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

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

然后,我們就要往這個 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)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
關(guān)閉
關(guān)閉