摘要:阻塞當進行讀寫時,線程是阻塞的狀態(tài)。當任何一個收到數(shù)據(jù)后,中斷程序?qū)酒疬M程。接收數(shù)據(jù)當收到數(shù)據(jù)后,中斷程序會給的就緒列表添加引用。當接收到數(shù)據(jù),中斷程序一方面修改,另一方面喚醒等待隊列中的進程,進程再次進入運行狀態(tài)如下圖。
本篇文章目的在于基本概念和原理的解釋,不會貼過多的使用代碼。
什么是NIOJava NIO (New IO)是 Java 的另一個 IO API (來自 java1.4) ,意味著可以替代標準的 Java IO API和 Java Networking API。 提供了一種與標準 IO API 不同的 IO 工作方式。
注意:Java的NIO只是說IO API,阻塞非阻塞才是IO的模型。
也有人稱NIO為No-Blocking IO,非阻塞IO,但是這么說并不嚴謹。因為對于基礎(chǔ)的IO操作API(比如文件IO,F(xiàn)ileChannel),還是阻塞的模型。只有對Networking IO API才可以使用非阻塞的模型(configureBlocking(false))。
Java NIO中的Networking IO API,支持非阻塞IO模型,還實現(xiàn)了IO多路復用(IO Multiplexing)。對于服務端來說,可以用更少的線程支持更多的并發(fā),大幅度提升了性能。
NIO中的阻塞與非阻塞阻塞與非阻塞是從線程的角度出發(fā)的,這里指的是線程狀態(tài)。
阻塞當進行IO讀寫時,線程是阻塞的狀態(tài)。此時會讓出cpu控制權(quán),不會占用cpu資源。
什么?不占用CPU資源?那是不是代表阻塞模型更好呢?
答案是并不是,雖然阻塞狀態(tài)不會占用CPU,但是會發(fā)生線程的切換,線程切換時會有上下文保存轉(zhuǎn)換的過程,需要CPU調(diào)度,是一個很昂貴的操作。
Java NIO中的基礎(chǔ)IO API(非Networking IO API)還是阻塞的方式,只是使用方式從面向流(stream)編程面向塊(buffer)了,和BIO本質(zhì)上并沒有什么區(qū)別。
非阻塞非阻塞是指在進行IO操作的時候,如果設(shè)備還未準備好(比如socket還沒有收到數(shù)據(jù)),操作會直接返回結(jié)果,不會讓當前線程進入阻塞狀態(tài)。
這樣的優(yōu)點是,使用者可以自行決定在數(shù)據(jù)未準備好時的操作。線程可以在沒有數(shù)據(jù)期間去執(zhí)行其他操作。
Networking API可以配置為非阻塞模型Channel.configureBlocking(false),配合Selector來實現(xiàn)多路復用功能。簡單的說就是一個Selector監(jiān)聽多個socket io(對于unix系統(tǒng)來說,socket也是一個fd,也屬于io),可以在一個線程中支持多個連接。當然在實際服務器開發(fā)時,就算是NIO模型,有些程序也不會只使用一個線程;但相比傳統(tǒng)的Blocking IO方式來說,需要的線程數(shù)量也會大大減少了。(redis中就是使用了IO多路復用技術(shù),并且只有一個線程監(jiān)聽socket io)
AIO 是 Java 1.7 之后引入的包,是 NIO 的升級版本,新增了提異步非阻塞的 IO 操作方式,所以人們叫它 AIO(Asynchronous IO),異步 IO 是基于事件和回調(diào)機制實現(xiàn)的,也就是應用操作之后會直接返回,不會堵塞在那里,當后臺處理完成,操作系統(tǒng)會執(zhí)行回調(diào)通知相應的線程進行后續(xù)的操作。
多路復用在I/O編程過程中,當需要同時處理多個客戶端請求時,可以利用多線程或者I/O多路復用技術(shù)進行處理。I/O多路復用技術(shù)通過把多個I/O的阻塞復用到同一個Select的阻塞上,從而使得系統(tǒng)在單線程的情況下可以同時處理多個客戶端請求。與傳統(tǒng)的多線程/多進程模型相比,I/O多路復用的最大優(yōu)勢是系統(tǒng)開銷小,系統(tǒng)不需要創(chuàng)建新的額外進程或者線程,也不需要維護這些線程和進程的運行,降低了系統(tǒng)的維護工作量,節(jié)省了系統(tǒng)的資源,I/O多路復用的主要應用場景如下:
服務器需要同時處理多個處于監(jiān)聽狀態(tài)或者多個連接狀態(tài)的Socket
服務器需要同時處理多種網(wǎng)絡協(xié)議的Socket
目前支持I/O多路復用的系統(tǒng)調(diào)用又select/pselect/poll/epoll。
select/epoll selectselect的實現(xiàn)思路很直接。假如程序同時監(jiān)視如下圖的sock1、sock2和sock3三個socket,那么在調(diào)用select之后,操作系統(tǒng)把進程A分別加入這三個socket的等待隊列中。
當任何一個socket收到數(shù)據(jù)后,中斷程序?qū)酒疬M程。下圖展示了sock2接收到了數(shù)據(jù)的處理流程。
所謂喚起進程,就是將進程從所有的等待隊列中移除,加入到工作隊列里面。如下圖所示。
經(jīng)由這些步驟,當進程A被喚醒后,它知道至少有一個socket接收了數(shù)據(jù)。程序只需遍歷一遍socket列表,就可以得到就緒的socket。
這種簡單方式行之有效,在幾乎所有操作系統(tǒng)都有對應的實現(xiàn)。
但是簡單的方法往往有缺點,主要是:
其一,每次調(diào)用select都需要將進程加入到所有監(jiān)視socket的等待隊列,每次喚醒都需要從每個隊列中移除。這里涉及了兩次遍歷,而且每次都要將整個fds列表傳遞給內(nèi)核,有一定的開銷。正是因為遍歷操作開銷大,出于效率的考量,才會規(guī)定select的最大監(jiān)視數(shù)量,默認只能監(jiān)視1024個socket。
其二,進程被喚醒后,程序并不知道哪些socket收到數(shù)據(jù),還需要遍歷一次。
那么,有沒有減少遍歷的方法?有沒有保存就緒socket的方法?這兩個問題便是epoll技術(shù)要解決的。
補充說明: 本節(jié)只解釋了select的一種情形。當程序調(diào)用select時,內(nèi)核會先遍歷一遍socket,如果有一個以上的socket接收緩沖區(qū)有數(shù)據(jù),那么select直接返回,不會阻塞。這也是為什么select的返回值有可能大于1的原因之一。如果沒有socket有數(shù)據(jù),進程才會阻塞。
select低效的原因之一是將“維護等待隊列”和“阻塞進程”兩個步驟合二為一。如下圖所示,每次調(diào)用select都需要這兩步操作,然而大多數(shù)應用場景中,需要監(jiān)視的socket相對固定,并不需要每次都修改。epoll將這兩個操作分開,先用epoll_ctl維護等待隊列,再調(diào)用epoll_wait阻塞進程。顯而易見的,效率就能得到提升。
select低效的另一個原因在于程序不知道哪些socket收到數(shù)據(jù),只能一個個遍歷。如果內(nèi)核維護一個“就緒列表”,引用收到數(shù)據(jù)的socket,就能避免遍歷。如下圖所示,計算機共有三個socket,收到數(shù)據(jù)的sock2和sock3被rdlist(就緒列表)所引用。當進程被喚醒后,只要獲取rdlist的內(nèi)容,就能夠知道哪些socket收到數(shù)據(jù)。
epollepoll是在select出現(xiàn)N多年后才被發(fā)明的,是select和poll的增強版本。epoll通過以下一些措施來改進效率。
原理:
創(chuàng)建epoll對象如下圖所示,當某個進程調(diào)用epoll_create方法時,內(nèi)核會創(chuàng)建一個eventpoll對象(也就是程序中epfd所代表的對象)。eventpoll對象也是文件系統(tǒng)中的一員,和socket一樣,它也會有等待隊列。
創(chuàng)建一個代表該epoll的eventpoll對象是必須的,因為內(nèi)核要維護“就緒列表”等數(shù)據(jù),“就緒列表”可以作為eventpoll的成員。
維護監(jiān)視列表創(chuàng)建epoll對象后,可以用epoll_ctl添加或刪除所要監(jiān)聽的socket。以添加socket為例,如下圖,如果通過epoll_ctl添加sock1、sock2和sock3的監(jiān)視,內(nèi)核會將eventpoll添加到這三個socket的等待隊列中。
當socket收到數(shù)據(jù)后,中斷程序會操作eventpoll對象,而不是直接操作進程。
接收數(shù)據(jù)當socket收到數(shù)據(jù)后,中斷程序會給eventpoll的“就緒列表”添加socket引用。如下圖展示的是sock2和sock3收到數(shù)據(jù)后,中斷程序讓rdlist引用這兩個socket。
eventpoll對象相當于是socket和進程之間的中介,socket的數(shù)據(jù)接收并不直接影響進程,而是通過改變eventpoll的就緒列表來改變進程狀態(tài)。
當程序執(zhí)行到epoll_wait時,如果rdlist已經(jīng)引用了socket,那么epoll_wait直接返回,如果rdlist為空,阻塞進程。
阻塞和喚醒進程假設(shè)計算機中正在運行進程A和進程B,在某時刻進程A運行到了epoll_wait語句。如下圖所示,內(nèi)核會將進程A放入eventpoll的等待隊列中,阻塞進程。
當socket接收到數(shù)據(jù),中斷程序一方面修改rdlist,另一方面喚醒eventpoll等待隊列中的進程,進程A再次進入運行狀態(tài)(如下圖)。也因為rdlist的存在,進程A可以知道哪些socket發(fā)生了變化。
參考Netty權(quán)威指南
https://zhuanlan.zhihu.com/p/...
http://tutorials.jenkov.com/j...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/75498.html
摘要:模塊什么是模塊什么是模塊化玩過游戲的朋友應該知道,一把裝配完整的步槍,一般是槍身消音器倍鏡握把槍托。更重要的是,其它大部分語言都支持模塊化。這一點與規(guī)范完全不同。模塊輸出的是值的緩存,不存在動態(tài)更新。 1.模塊 1.1 什么是模塊?什么是模塊化? 玩過FPS游戲的朋友應該知道,一把裝配完整的M4步槍,一般是槍身+消音器+倍鏡+握把+槍托。 如果把M4步槍看成是一個頁面的話,那么我們可以...
必須要看的前言 本文風格:以??簡單易懂??的語言帶你徹底搞懂KNN,了解什么是有監(jiān)督學習算法。 認真看完這篇文章,徹底了解KNN、了解監(jiān)督學習算法絕對是一樣很簡單的事情。 注:本篇文章非常詳細,同時我也附加了Python代碼,歡迎收藏后慢慢閱讀。 目錄 必須要看的前言監(jiān)督學習算法KNN/K近鄰算法1 算法原理1.1 實現(xiàn)過程1.2 距離的確定 2 算法的優(yōu)缺點3 算法的變種3.1 變...
摘要:的選擇器允許單個線程監(jiān)視多個輸入通道。一旦執(zhí)行的線程已經(jīng)超過讀取代碼中的某個數(shù)據(jù)片段,該線程就不會在數(shù)據(jù)中向后移動通常不會。 1、引言 很多初涉網(wǎng)絡編程的程序員,在研究Java NIO(即異步IO)和經(jīng)典IO(也就是常說的阻塞式IO)的API時,很快就會發(fā)現(xiàn)一個問題:我什么時候應該使用經(jīng)典IO,什么時候應該使用NIO? 在本文中,將嘗試用簡明扼要的文字,闡明Java NIO和經(jīng)典IO之...
摘要:分布式高并發(fā)微服務問阿里京東螞蟻等大廠面試真題解析道跳槽漲薪必備精選面試題最新版大廠面試真題集點擊這里免費領(lǐng)取點擊這里免費領(lǐng)取 估計很多Java程序員平時主要的工作就是一些Web系統(tǒng)的業(yè)務開發(fā),對于服務端IO程序以及網(wǎng)絡通信編程做得并不多,但是對于高級或者資深程序員來說,IO通信以及服務端編...
閱讀 828·2021-10-09 09:44
閱讀 721·2019-08-30 13:55
閱讀 3179·2019-08-29 15:07
閱讀 3249·2019-08-29 13:09
閱讀 2435·2019-08-29 11:10
閱讀 1316·2019-08-26 14:05
閱讀 3630·2019-08-26 13:57
閱讀 2228·2019-08-23 16:42