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

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

堅持思考,就會很酷


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


IO 多路復(fù)用


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

那這個問題怎么解決?

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

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

因為還不夠高級。for 循環(huán)每次要定期 sleep 1s,這個會導(dǎo)致吞吐能力極差,因為很可能在剛好要 sleep 的時候,所有的 fd 都準備好 IO 數(shù)據(jù),而這個時候卻要硬生生的等待 1s,可想而知。。。

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

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

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

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

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

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

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


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

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

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

為什么有 3 種?

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



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

好像沒有了!

這 3 種到底是做啥的?

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

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

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

舉個例子,以 select 和 epoll 來對比舉例,池子里管理了 1024 個句柄,loop 線程被喚醒的時候,select 都是蒙的,都不知道這 1024 個 fd 里誰 IO 準備好了。這種情況怎么辦?只能遍歷這 1024 個 fd ,一個個測試。假如只有一個句柄準備好了,那相當于做了 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 負責創(chuàng)建一個池子,一個監(jiān)控和管理句柄 fd 的池子;
  • epollctl 負責管理這個池子里的 fd 增、刪、改;
  • epollwait 就是負責打盹的,讓出 CPU 調(diào)度,但是只要有“事”,立馬會從這里喚醒;

?2???epoll 高效的原理

Linux 下,epoll 一直被吹爆,作為高并發(fā) IO 實現(xiàn)的秘密武器。其中原理其實非常樸實:epoll 的實現(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é)其中細節(jié)。我們拿到了一個 epollfd ,這個 epollfd 就能唯一代表這個 epoll 池。注意,這里又有一個細節(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)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
關(guān)閉
關(guān)閉