日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

java io复用_学习Java编程-IO复用

發布時間:2024/4/14 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java io复用_学习Java编程-IO复用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原標題:學習Java編程-IO復用

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

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

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

高并發編程方法當然就是把兩個階段分開處理。即,等待消息準備好的代碼段,與處理消息的代碼段是分離的。當然,這也要求套接字必須是非阻塞的,否則,處理消息的代碼段很容易導致條件不滿足時,所在線程又進入了睡眠等待階段。那么問題來了,等待消息準備好這個階段怎么實現?它畢竟還是等待,這意味著線程還是要睡眠的!解決辦法就是,主動查詢,或者讓1個線程為所有連接而等待!這就是IO多路復用了。多路復用就是處理等待消息準備好這件事的,但它可以同時處理多個連接!它也可以“等待”,所以它也可能導致線程睡眠,然而這不要緊,因為它一對多、它可以監控所有連接。這樣,當我們的線程被喚醒執行時,就一定是有一些連接準備好被我們的代碼執行了,這是有效率的!沒有那么多個線程都在爭搶處理“等待消息準備好”階段,整個世界終于清凈了!

多路復用有很多種實現,在linux上,2.4內核前主要是select和poll,現在主流是epoll,它們的使用方法似乎很不同,但本質是一樣的。

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

簡單的談下epoll為何會替代select。

前面提到過,高并發的核心解決方案是1個線程處理所有連接的“等待消息準備好”,這一點上epoll和select是無爭議的。但select預估錯誤了一件事,就像我們開篇所說,當數十萬并發連接存在時,可能每一毫秒只有數百個活躍的連接,同時其余數十萬連接在這一毫秒是非活躍的。select的使用方法是這樣的:

返回的活躍連接==select(全部待監控的連接)

什么時候會調用select方法呢?在你認為需要找出有報文到達的活躍連接時,就應該調用。所以,調用select在高并發時是會被頻繁調用的。這樣,這個頻繁調用的方法就很有必要看看它是否有效率,因為,它的輕微效率損失都會被“頻繁”二字所放大。它有效率損失嗎?顯而易見,全部待監控連接是數以十萬計的,返回的只是數百個活躍連接,這本身就是無效率的表現。被放大后就會發現,處理并發上萬個連接時,select就完全力不從心了。

看幾個圖。當并發連接為一千以下,select的執行次數不算頻繁,與epoll似乎并無多少差距:

然而,并發數一旦上去,select的缺點被“執行頻繁”無限放大了,且并發數越多越明顯:

再來說說epoll是如何解決的。它很聰明的用了3個方法來實現select方法要做的事:

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

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

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

這么做的好處主要是:分清了頻繁調用和不頻繁調用的操作。例如,epoll_ctrl是不太頻繁調用的,而epoll_wait是非常頻繁調用的。這時,epoll_wait卻幾乎沒有入參,這比select的效率高出一大截,而且,它也不會隨著并發連接的增加使得入參越發多起來,導致內核執行效率下降。

epoll是怎么實現的呢?其實很簡單,從這3個方法就可以看出,它比select聰明的避免了每次頻繁調用“哪些連接已經處在消息準備好階段”的epoll_wait時,是不需要把所有待監控連接傳入的。這意味著,它在內核態維護了一個數據結構保存著所有待監控的連接。這個數據結構就是一棵紅黑樹,它的結點的增加、減少是通過epoll_ctrl來完成的。它是非常簡單的:

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

最后,再看看epoll提供的2種玩法ET和LT,即翻譯過來的邊緣觸發和水平觸發。其實這兩個中文名字倒也有些貼切。這2種使用方式針對的仍然是效率問題,只不過變成了epoll_wait返回的連接如何能夠更準確些。

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

于是,ET和LT模式就應運而生了。LT是每次滿足期待狀態的連接,都得在epoll_wait中返回,所以它一視同仁,都在一條水平線上。ET則不然,它傾向更精確的返回連接。在上面的例子中,連接第一次變為可寫后,若是程序未向連接上寫入任何數據,那么下一次epoll_wait是不會返回這個連接的。ET叫做邊緣觸發,就是指,只有連接從一個狀態轉到另一個狀態時,才會觸發epoll_wait返回它。可見,ET的編程要復雜不少,至少應用程序要小心的防止epoll_wait的返回的連接出現:可寫時未寫數據后卻期待下一次“可寫”、可讀時未讀盡數據卻期待下一次“可讀”。

當然,從一般應用場景上它們性能是不會有什么大的差距的,ET可能的優點是,epoll_wait的調用次數會減少一些,某些場景下連接在不必要喚醒時不會被喚醒(此喚醒指epoll_wait返回)。但如果像我上面舉例所說的,有時它不單純是一個網絡問題,跟應用場景相關。當然,大部分開源框架都是基于ET寫的,框架嘛,它追求的是純技術問題,當然力求盡善盡美返回搜狐,查看更多

責任編輯:

總結

以上是生活随笔為你收集整理的java io复用_学习Java编程-IO复用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。