一文帶你讀懂JavaIO復(fù)用-創(chuàng)新互聯(lián)

本篇文章為大家展示了一文帶你讀懂Java IO復(fù)用,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。

創(chuàng)新互聯(lián)建站自2013年起,先為鎮(zhèn)寧等服務(wù)建站,鎮(zhèn)寧等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為鎮(zhèn)寧企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。

對(duì)于服務(wù)器的并發(fā)處理能力,我們需要的是:每一毫秒服務(wù)器都能及時(shí)處理這一毫秒內(nèi)收到的數(shù)百個(gè)不同TCP連接上的報(bào)文,與此同時(shí),可能服務(wù)器上還有數(shù)以十萬(wàn)計(jì)的最近幾秒沒有收發(fā)任何報(bào)文的相對(duì)不活躍連接。同時(shí)處理多個(gè)并行發(fā)生事件的連接,簡(jiǎn)稱為并發(fā);同時(shí)處理萬(wàn)計(jì)、十萬(wàn)計(jì)的連接,則是高并發(fā)。服務(wù)器的并發(fā)編程所追求的就是處理的并發(fā)連接數(shù)目無(wú)限大,同時(shí)維持著高效率使用CPU等資源,直至物理資源首先耗盡。

并發(fā)編程有很多種實(shí)現(xiàn)模型,最簡(jiǎn)單的就是與“線程”捆綁,1個(gè)線程處理1個(gè)連接的全部生命周期。優(yōu)點(diǎn):這個(gè)模型足夠簡(jiǎn)單,它可以實(shí)現(xiàn)復(fù)雜的業(yè)務(wù)場(chǎng)景,同時(shí),線程個(gè)數(shù)是可以遠(yuǎn)大于CPU個(gè)數(shù)的。然而,線程個(gè)數(shù)又不是可以無(wú)限增大的,為什么呢?因?yàn)榫€程什么時(shí)候執(zhí)行是由操作系統(tǒng)內(nèi)核調(diào)度算法決定的,調(diào)度算法并不會(huì)考慮某個(gè)線程可能只是為了一個(gè)連接服務(wù)的,它會(huì)做大一統(tǒng)的玩法:時(shí)間片到了就執(zhí)行一下,哪怕這個(gè)線程一執(zhí)行就會(huì)不得不繼續(xù)睡眠。這樣來(lái)回的喚醒、睡眠線程在次數(shù)不多的情況下,是廉價(jià)的,但如果操作系統(tǒng)的線程總數(shù)很多時(shí),它就是昂貴的(被放大了),因?yàn)檫@種技術(shù)性的調(diào)度損耗會(huì)影響到線程上執(zhí)行的業(yè)務(wù)代碼的時(shí)間。舉個(gè)例子,這時(shí)大部分擁有不活躍連接的線程就像我們的國(guó)企,它們執(zhí)行效率太低了,它總是喚醒就睡眠在做無(wú)用功,而它喚醒爭(zhēng)到CPU資源的同時(shí),就意味著處理活躍連接的民企線程減少獲得了CPU的機(jī)會(huì),CPU是核心競(jìng)爭(zhēng)力,它的無(wú)效率進(jìn)而影響了GDP總吞吐量。我們所追求的是并發(fā)處理數(shù)十萬(wàn)連接,當(dāng)幾千個(gè)線程出現(xiàn)時(shí),系統(tǒng)的執(zhí)行效率就已經(jīng)無(wú)法滿足高并發(fā)了。

對(duì)高并發(fā)編程,目前只有一種模型,也是本質(zhì)上唯一有效的玩法。連接上的消息處理,可以分為兩個(gè)階段:等待消息準(zhǔn)備好、消息處理。當(dāng)使用默認(rèn)的阻塞套接字時(shí)(例如上面提到的1個(gè)線程捆綁處理1個(gè)連接),往往是把這兩個(gè)階段合而為一,這樣操作套接字的代碼所在的線程就得睡眠來(lái)等待消息準(zhǔn)備好,這導(dǎo)致了高并發(fā)下線程會(huì)頻繁的睡眠、喚醒,從而影響了CPU的使用效率。

高并發(fā)編程方法當(dāng)然就是把兩個(gè)階段分開處理。即,等待消息準(zhǔn)備好的代碼段,與處理消息的代碼段是分離的。當(dāng)然,這也要求套接字必須是非阻塞的,否則,處理消息的代碼段很容易導(dǎo)致條件不滿足時(shí),所在線程又進(jìn)入了睡眠等待階段。那么問(wèn)題來(lái)了,等待消息準(zhǔn)備好這個(gè)階段怎么實(shí)現(xiàn)?它畢竟還是等待,這意味著線程還是要睡眠的!解決辦法就是,主動(dòng)查詢,或者讓1個(gè)線程為所有連接而等待!這就是IO多路復(fù)用了。多路復(fù)用就是處理等待消息準(zhǔn)備好這件事的,但它可以同時(shí)處理多個(gè)連接!它也可以“等待”,所以它也可能導(dǎo)致線程睡眠,然而這不要緊,因?yàn)樗粚?duì)多、它可以監(jiān)控所有連接。這樣,當(dāng)我們的線程被喚醒執(zhí)行時(shí),就一定是有一些連接準(zhǔn)備好被我們的代碼執(zhí)行了,這是有效率的!沒有那么多個(gè)線程都在爭(zhēng)搶處理“等待消息準(zhǔn)備好”階段,整個(gè)世界終于清凈了!
多路復(fù)用有很多種實(shí)現(xiàn),在linux上,2.4內(nèi)核前主要是select和poll,現(xiàn)在主流是epoll,它們的使用方法似乎很不同,但本質(zhì)是一樣的。

效率卻也不同,這也是epoll完全替代了select的原因。

簡(jiǎn)單的談下epoll為何會(huì)替代select。

前面提到過(guò),高并發(fā)的核心解決方案是1個(gè)線程處理所有連接的“等待消息準(zhǔn)備好”,這一點(diǎn)上epoll和select是無(wú)爭(zhēng)議的。但select預(yù)估錯(cuò)誤了一件事,就像我們開篇所說(shuō),當(dāng)數(shù)十萬(wàn)并發(fā)連接存在時(shí),可能每一毫秒只有數(shù)百個(gè)活躍的連接,同時(shí)其余數(shù)十萬(wàn)連接在這一毫秒是非活躍的。select的使用方法是這樣的:
返回的活躍連接 ==select(全部待監(jiān)控的連接)

什么時(shí)候會(huì)調(diào)用select方法呢?在你認(rèn)為需要找出有報(bào)文到達(dá)的活躍連接時(shí),就應(yīng)該調(diào)用。所以,調(diào)用select在高并發(fā)時(shí)是會(huì)被頻繁調(diào)用的。這樣,這個(gè)頻繁調(diào)用的方法就很有必要看看它是否有效率,因?yàn)?,它的輕微效率損失都會(huì)被“頻繁”二字所放大。它有效率損失嗎?顯而易見,全部待監(jiān)控連接是數(shù)以十萬(wàn)計(jì)的,返回的只是數(shù)百個(gè)活躍連接,這本身就是無(wú)效率的表現(xiàn)。被放大后就會(huì)發(fā)現(xiàn),處理并發(fā)上萬(wàn)個(gè)連接時(shí),select就完全力不從心了。
看幾個(gè)圖。當(dāng)并發(fā)連接為一千以下,select的執(zhí)行次數(shù)不算頻繁,與epoll似乎并無(wú)多少差距: 

一文帶你讀懂Java IO復(fù)用

然而,并發(fā)數(shù)一旦上去,select的缺點(diǎn)被“執(zhí)行頻繁”無(wú)限放大了,且并發(fā)數(shù)越多越明顯:

一文帶你讀懂Java IO復(fù)用

再來(lái)說(shuō)說(shuō)epoll是如何解決的。它很聰明的用了3個(gè)方法來(lái)實(shí)現(xiàn)select方法要做的事:

新建的epoll描述符==epoll_create()

epoll_ctrl(epoll描述符,添加或者刪除所有待監(jiān)控的連接)

返回的活躍連接 ==epoll_wait( epoll描述符 )

這么做的好處主要是:分清了頻繁調(diào)用和不頻繁調(diào)用的操作。例如,epoll_ctrl是不太頻繁調(diào)用的,而epoll_wait是非常頻繁調(diào)用的。這時(shí),epoll_wait卻幾乎沒有入?yún)?,這比select的效率高出一大截,而且,它也不會(huì)隨著并發(fā)連接的增加使得入?yún)⒃桨l(fā)多起來(lái),導(dǎo)致內(nèi)核執(zhí)行效率下降。

epoll是怎么實(shí)現(xiàn)的呢?其實(shí)很簡(jiǎn)單,從這3個(gè)方法就可以看出,它比select聰明的避免了每次頻繁調(diào)用“哪些連接已經(jīng)處在消息準(zhǔn)備好階段”的 epoll_wait時(shí),是不需要把所有待監(jiān)控連接傳入的。這意味著,它在內(nèi)核態(tài)維護(hù)了一個(gè)數(shù)據(jù)結(jié)構(gòu)保存著所有待監(jiān)控的連接。這個(gè)數(shù)據(jù)結(jié)構(gòu)就是一棵紅黑樹,它的結(jié)點(diǎn)的增加、減少是通過(guò)epoll_ctrl來(lái)完成的。它是非常簡(jiǎn)單的: 

一文帶你讀懂Java IO復(fù)用

圖中左下方的紅黑樹由所有待監(jiān)控的連接構(gòu)成。左上方的鏈表,同是目前所有活躍的連接。于是,epoll_wait執(zhí)行時(shí)只是檢查左上方的鏈表,并返回左上方鏈表中的連接給用戶。這樣,epoll_wait的執(zhí)行效率能不高嗎?

最后,再看看epoll提供的2種玩法ET和LT,即翻譯過(guò)來(lái)的邊緣觸發(fā)和水平觸發(fā)。其實(shí)這兩個(gè)中文名字倒也有些貼切。這2種使用方式針對(duì)的仍然是效率問(wèn)題,只不過(guò)變成了epoll_wait返回的連接如何能夠更準(zhǔn)確些。

例如,我們需要監(jiān)控一個(gè)連接的寫緩沖區(qū)是否空閑,滿足“可寫”時(shí)我們就可以從用戶態(tài)將響應(yīng)調(diào)用write發(fā)送給客戶端 。但是,或者連接可寫時(shí),我們的“響應(yīng)”內(nèi)容還在磁盤上呢,此時(shí)若是磁盤讀取還未完成呢?肯定不能使線程阻塞的,那么就不發(fā)送響應(yīng)了。但是,下一次epoll_wait時(shí)可能又把這個(gè)連接返回給你了,你還得檢查下是否要處理??赡?,我們的程序有另一個(gè)模塊專門處理磁盤IO,它會(huì)在磁盤IO完成時(shí)再發(fā)送響應(yīng)。那么,每次epoll_wait都返回這個(gè)“可寫”的、卻無(wú)法立刻處理的連接,是否符合用戶預(yù)期呢?

于是,ET和LT模式就應(yīng)運(yùn)而生了。LT是每次滿足期待狀態(tài)的連接,都得在epoll_wait中返回,所以它一視同仁,都在一條水平線上。ET則不然,它傾向更精確的返回連接。在上面的例子中,連接第一次變?yōu)榭蓪懞螅羰浅绦蛭聪蜻B接上寫入任何數(shù)據(jù),那么下一次epoll_wait是不會(huì)返回這個(gè)連接的。ET叫做 邊緣觸發(fā),就是指,只有連接從一個(gè)狀態(tài)轉(zhuǎn)到另一個(gè)狀態(tài)時(shí),才會(huì)觸發(fā)epoll_wait返回它。可見,ET的編程要復(fù)雜不少,至少應(yīng)用程序要小心的防止epoll_wait的返回的連接出現(xiàn):可寫時(shí)未寫數(shù)據(jù)后卻期待下一次“可寫”、可讀時(shí)未讀盡數(shù)據(jù)卻期待下一次“可讀”。

當(dāng)然,從一般應(yīng)用場(chǎng)景上它們性能是不會(huì)有什么大的差距的,ET可能的優(yōu)點(diǎn)是,epoll_wait的調(diào)用次數(shù)會(huì)減少一些,某些場(chǎng)景下連接在不必要喚醒時(shí)不會(huì)被喚醒(此喚醒指epoll_wait返回)。但如果像我上面舉例所說(shuō)的,有時(shí)它不單純是一個(gè)網(wǎng)絡(luò)問(wèn)題,跟應(yīng)用場(chǎng)景相關(guān)。當(dāng)然,大部分開源框架都是基于ET寫的,框架嘛,它追求的是純技術(shù)問(wèn)題,當(dāng)然力求盡善盡美

上述內(nèi)容就是一文帶你讀懂Java IO復(fù)用,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

網(wǎng)頁(yè)題目:一文帶你讀懂JavaIO復(fù)用-創(chuàng)新互聯(lián)
文章源于:http://muchs.cn/article16/hisdg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營(yíng)銷推廣、網(wǎng)站改版、網(wǎng)站設(shè)計(jì)公司網(wǎng)站營(yíng)銷、App設(shè)計(jì)微信小程序

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁(yè)設(shè)計(jì)