<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          阻塞、非阻塞、多路復(fù)用、同步、異步、BIO、NIO、AIO 一鍋端

          共 12751字,需瀏覽 26分鐘

           ·

          2021-05-11 01:33

          承接上文的操作系統(tǒng),關(guān)于IO會涉及到阻塞、非阻塞、多路復(fù)用、同步、異步、BIO、NIO、AIO等幾個知識點。知識點雖然不難但平常經(jīng)常容易搞混,特此Mark下,與君共勉。

          1 阻塞跟非阻塞

          1.1 阻塞

          阻塞IO

          阻塞IO情況下,當(dāng)用戶調(diào)用read后,用戶線程會被阻塞,等內(nèi)核數(shù)據(jù)準(zhǔn)備好并且數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶態(tài)緩存區(qū)后read才會返回。可以看到是阻塞的兩個部分。

          1. CPU把數(shù)據(jù)從磁盤讀到內(nèi)核緩沖區(qū)。

          2. CPU把數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶緩沖區(qū)。

          1.2 非阻塞

          非阻塞IO

          非阻塞IO發(fā)出read請求后發(fā)現(xiàn)數(shù)據(jù)沒準(zhǔn)備好,會繼續(xù)往下執(zhí)行,此時應(yīng)用程序會不斷輪詢polling內(nèi)核詢問數(shù)據(jù)是否準(zhǔn)備好,當(dāng)數(shù)據(jù)沒有準(zhǔn)備好時,內(nèi)核立即返回EWOULDBLOCK錯誤。直到數(shù)據(jù)被拷貝到應(yīng)用程序緩沖區(qū),read請求才獲取到結(jié)果。并且你要注意!這里最后一次 read 調(diào)用獲取數(shù)據(jù)的過程,是一個同步的過程,是需要等待的過程。這里的同步指的是內(nèi)核態(tài)的數(shù)據(jù)拷貝到用戶程序的緩存區(qū)這個過程

          1.3 IO多路復(fù)用

          IO多路復(fù)用

          非阻塞情況下無可用數(shù)據(jù)時,應(yīng)用程序每次輪詢內(nèi)核看數(shù)據(jù)是否準(zhǔn)備好了也耗費CPU,能否不讓它輪詢,當(dāng)內(nèi)核緩沖區(qū)數(shù)據(jù)準(zhǔn)備好了,以事件通知當(dāng)機制告知應(yīng)用進程數(shù)據(jù)準(zhǔn)備好了呢?應(yīng)用進程在沒有收到數(shù)據(jù)準(zhǔn)備好的事件通知信號時可以忙寫其他的工作。此時IO多路復(fù)用就派上用場了。

          IO多路復(fù)用中文比較讓人頭大,IO多路復(fù)用的原文叫 I/O multiplexing,這里的 multiplexing 指的其實是在單個線程通過記錄跟蹤每一個Sock(I/O流)的狀態(tài)來同時管理多個I/O流. 發(fā)明它的目的是盡量多的提高服務(wù)器的吞吐能力。實現(xiàn)一個線程監(jiān)控多個IO請求,哪個IO有請求就把數(shù)據(jù)從內(nèi)核拷貝到進程緩沖區(qū),拷貝期間是阻塞的!現(xiàn)在已經(jīng)可以通過采用mmap地址映射的方法,達到內(nèi)存共享效果,避免真復(fù)制,提高效率。

          IO多路復(fù)用

          select、poll、epoll 都是I/O多路復(fù)用的具體的實現(xiàn)。
          1.3.1 select

          select是第一版IO復(fù)用,提出后暴漏了很多問題。

          1. select 會修改傳入的參數(shù)數(shù)組,這個對于一個需要調(diào)用很多次的函數(shù),是非常不友好的。

          2. select 如果任何一個sock(I/O stream)出現(xiàn)了數(shù)據(jù),select 僅僅會返回,但不會告訴是那個sock上有數(shù)據(jù),只能自己遍歷查找。

          3. select 只能監(jiān)視1024個鏈接。

          4. select 不是線程安全的,如果你把一個sock加入到select, 然后突然另外一個線程發(fā)現(xiàn)這個sock不用,要收回,這個select 不支持的。

          1.3.2 poll

          poll 修復(fù)了 select 的很多問題。

          1. poll 去掉了1024個鏈接的限制。

          2. poll 從設(shè)計上來說不再修改傳入數(shù)組。

          但是poll仍然不是線程安全的, 這就意味著不管服務(wù)器有多強悍,你也只能在一個線程里面處理一組 I/O 流。你當(dāng)然可以拿多進程來配合了,不過然后你就有了多進程的各種問題。

          1.3.3 epoll

          epoll 可以說是 I/O  多路復(fù)用最新的一個實現(xiàn),epoll 修復(fù)了poll 和select絕大部分問題, 比如:

          1. epoll 現(xiàn)在是線程安全的。

          2. epoll 現(xiàn)在不僅告訴你sock組里面數(shù)據(jù),還會告訴你具體哪個sock有數(shù)據(jù),你不用自己去找了。

          3. epoll 內(nèi)核態(tài)管理了各種IO文件描述符, 以前用戶態(tài)發(fā)送所有文件描述符到內(nèi)核態(tài),然后內(nèi)核態(tài)負責(zé)篩選返回可用數(shù)組,現(xiàn)在epoll模式下所有文件描述符在內(nèi)核態(tài)有存,查詢時不用傳文件描述符進去了。

          1.3.4 三者對比
          對比圖

          橫軸 Dead connections 是鏈接數(shù)的意思,叫這個名字只是它的測試工具叫deadcon。縱軸是每秒處理請求的數(shù)量,可看到epoll每秒處理請求的數(shù)量基本不會隨著鏈接變多而下降的。poll 和/dev/poll 就很慘了。但 epoll 有個致命的缺點是只有linux支持。

          比如平常Nginx為何可以支持4W的QPS是因為它會使用目標(biāo)平臺上面最高效的I/O多路復(fù)用模型。

          1.4 異步IO

          異步IO

          然后你會發(fā)現(xiàn)上面的提到過的操作都不是真正的異步,因為兩個階段總要等待會兒!而真正的異步 I/O 是內(nèi)核數(shù)據(jù)準(zhǔn)備好和數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài)這兩個過程都不用等待。

          很慶幸,Linux給我們準(zhǔn)備了aio_readaio_write函數(shù)實現(xiàn)真實的異步,當(dāng)用戶發(fā)起aio_read請求后就會自動返回。內(nèi)核會自動將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶進程空間,應(yīng)用進程啥都不用管。

          2 同步跟異步

          2.1 同步

          同步跟異步的區(qū)別在于數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間是否由用戶線程完成,這里又分為同步阻塞跟同步非阻塞兩種。

          1. 同步阻塞:此時一個線程維護一個連接,該線程完成數(shù)據(jù)到讀寫跟處理到全部過程,數(shù)據(jù)讀寫時時線程是被阻塞的。

          2. 同步非阻塞:非阻塞的意思是用戶線程發(fā)出讀請求后,讀請求不會阻塞當(dāng)前用戶線程,不過用戶線程還是要不斷的去主動判斷數(shù)據(jù)是否準(zhǔn)備OK了。此時還是會阻塞等待內(nèi)核復(fù)制數(shù)據(jù)到用戶進程。他與同步BIO區(qū)別是使用一個連接全程等待

          我們以同步非阻塞為例,如下可看到,在將數(shù)據(jù)從內(nèi)核拷貝到用戶空間這一過程,是由用戶線程阻塞完成的。

          同步非阻塞

          2.2 異步

          對于異步來說,用戶進行讀或者寫后,將立刻返回,由內(nèi)核去完成數(shù)據(jù)讀取以及拷貝工作,完成后通知用戶,并執(zhí)行回調(diào)函數(shù)(用戶提供的callback),此時數(shù)據(jù)已從內(nèi)核拷貝到用戶空間,用戶線程只需要對數(shù)據(jù)進行處理即可,不需要關(guān)注讀寫,用戶不需要等待內(nèi)核對數(shù)據(jù)的復(fù)制操作,用戶在得到通知時數(shù)據(jù)已經(jīng)被復(fù)制到用戶空間。我們以如下的真實異步非阻塞為例。

          異步IO

          可發(fā)現(xiàn),用戶在調(diào)用之后會立即返回,由內(nèi)核完成數(shù)據(jù)的拷貝工作,并通知用戶線程,進行回調(diào)。

          2.3 同步跟異步對比

          同步關(guān)注的消息通信機制synchronous communication,在發(fā)出一個調(diào)用時,在沒有得到結(jié)果之前,該調(diào)用就不返回。但是一旦調(diào)用返回,就得到返回值了。換句話說,就是由調(diào)用者主動等待這個調(diào)用的結(jié)果。

          異步關(guān)注消息通信機制asynchronous communication,調(diào)用在發(fā)出之后,這個調(diào)用就直接返回了,所以沒有返回結(jié)果。換句話說,當(dāng)一個異步過程調(diào)用發(fā)出后,調(diào)用者不會立刻得到結(jié)果。而是在調(diào)用發(fā)出后,被調(diào)用者通過狀態(tài)、通知來通知調(diào)用者,或通過回調(diào)函數(shù)處理這個調(diào)用。

          3 Java IO

          在Java中,我們使用socket進行網(wǎng)絡(luò)通信,IO主要有三種模式,主要看內(nèi)核支持哪些。

          1. BIO:同步阻塞IO。

          2. NIO:同步非阻塞IO。

          3. AIO:異步非阻塞IO。

          3.1 BIO

          同步阻塞IO,每個客戶端的Socket連接請求,服務(wù)端都會對應(yīng)有個處理線程與之對應(yīng),對于沒有分配到處理線程的連接就會被阻塞或者拒絕。相當(dāng)于是一個連接一個線程

          BIO

          BIO特點

          1. 使用一個獨立的線程維護一個socket連接,隨著連接數(shù)量的增多,對虛擬機造成一定壓力。

          2. 使用流來讀取數(shù)據(jù),流是阻塞的,當(dāng)沒有可讀/可寫數(shù)據(jù)時,線程等待,會造成資源的浪費。

          3.1.1 BIO 樣例

          常量:

          public class Constant {
              public static final String HOST = "127.0.0.1";
              public static final int PORT = 8080;
          }

          主類:

          public class ClientMain {
              public static void main(String[] args) {
                  //開啟服務(wù)
                  System.out.println("開啟服務(wù),監(jiān)聽端口:" + Constant.PORT);
                  new Thread(new ServerThread()).start();
                  //建立一個socket客戶端,發(fā)起請求
                  System.out.println("客戶端,請求連接,并發(fā)送數(shù)據(jù)");
                  try {
                      Socket socket = new Socket(Constant.HOST,Constant.PORT);
                      //開啟新的線程處理socket連接
                      new Thread(new ClientProcessThread(socket)).start();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }

              }
          }

          服務(wù)端監(jiān)聽線程:

          // 開啟服務(wù)監(jiān)聽線程,當(dāng)收到連接請求后,開啟新的線程進行處理
          public class ServerThread implements Runnable{
              @Override
              public void run() {
                  try {
                      ServerSocket serverSocket = new ServerSocket(Constant.PORT);
                      while (true){
                          Socket socket = serverSocket.accept();
                          new Thread(new ServerProcessThread(socket)).start();
                          //開啟新的線程進行連接請求的處理
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }

          服務(wù)端處理線程:

          import java.io.*;
          import java.net.Socket;
          /**
           * 服務(wù)端收到連接請求后,處理請求的線程,阻塞式IO
           */

          public class ServerProcessThread implements Runnable {
              private Socket socket;
              public ServerProcessThread(Socket socket){
                  this.socket = socket;
              }
              @Override
              public void run() {
                  //獲取客戶端的數(shù)據(jù),并寫回
                  //等待響應(yīng)
                  try {
                      BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                      String line = "";
                      String requestStr = "";
                      System.out.println("來自客戶端的數(shù)據(jù):"); // 讀取客戶端數(shù)據(jù)
                      while((line = bufferedReader.readLine()) != null){
                          requestStr += line;
                          System.out.println(line);
                      }
                      //  從服務(wù)端發(fā)給客戶端數(shù)據(jù)
                      Writer writer = new OutputStreamWriter(socket.getOutputStream());
                      writer.write("data from server " + requestStr + "\r\n");
                      writer.flush();
                      writer.close();
                      bufferedReader.close();
                      socket.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }

          客戶端:

          /**
           * 維護客戶端socket連接的線程,阻塞式IO
           */

          public class ClientProcessThread implements Runnable {
              private Socket socket;
              public ClientProcessThread(Socket socket){
                  this.socket = socket;
              }
              @Override
              public void run() {
                  //寫數(shù)據(jù),等待響應(yīng),輸出響應(yīng)
                  String requestStr = "data from client \r\n";
                  try {
                      Writer writer = new OutputStreamWriter(socket.getOutputStream());
                      writer.write(requestStr);
                      writer.flush();
                      socket.shutdownOutput();
                      //等待響應(yīng)
                      BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                      String line;
                      System.out.println("來自服務(wù)端的響應(yīng):");
                      while((line = bufferedReader.readLine()) != null){
                          System.out.println(line);
                      }
                      writer.close();
                      bufferedReader.close();
                      socket.close();

                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
          輸出結(jié)果:

          3.2 NIO

          同步非阻塞IO之NIO:服務(wù)器端保存一個Socket連接列表,然后對這個列表進行輪詢,如果發(fā)現(xiàn)某個Socket端口上有數(shù)據(jù)可讀時說明讀就緒,則調(diào)用該socket連接的相應(yīng)讀操作。如果發(fā)現(xiàn)某個 Socket端口上有數(shù)據(jù)可寫時說明寫就緒,則調(diào)用該socket連接的相應(yīng)寫操作。如果某個端口的Socket連接已經(jīng)中斷,則調(diào)用相應(yīng)的析構(gòu)方法關(guān)閉該端口。這樣能充分利用服務(wù)器資源,效率得到了很大提高,在進行IO操作請求時候再用個線程去處理,是一個請求一個線程。Java中使用Selector、Channel、Buffer來實現(xiàn)上述效果。

          NIO

          每個線程中包含一個Selector對象,它相當(dāng)于一個通道管理器,可以實現(xiàn)在一個線程中處理多個通道的目的,減少線程的創(chuàng)建數(shù)量。遠程連接對應(yīng)一個channel,數(shù)據(jù)的讀寫通過buffer均在同一個channel中完成,并且數(shù)據(jù)的讀寫是非阻塞的。通道創(chuàng)建后需要注冊在selector中,同時需要為該通道注冊感興趣事件(客戶端連接服務(wù)端事件、服務(wù)端接收客戶端連接事件、讀事件、寫事件),selector線程需要采用輪訓(xùn)的方式調(diào)用selectorselect函數(shù),直到所有注冊通道中有興趣的事件發(fā)生,則返回,否則一直阻塞。而后循環(huán)處理所有就緒的感興趣事件。以上步驟解決BIO的兩個瓶頸:

          1. 不必對每個連接分別創(chuàng)建線程。

          2. 數(shù)據(jù)讀寫非阻塞。

          下面對以下三個概念做一個簡單介紹,Java NIO由以下三個核心部分組成:

          1. selector:Selector 允許單線程處理多個Channel。如果你的應(yīng)用打開了多個連接(通道),但每個連接的流量都很低,使用Selector就會很方便。要使用Selector,得向Selector注冊Channel,然后調(diào)用他的select方法,這個方法會一直阻塞到某個注冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件,事件的例子入有新連接接進來,數(shù)據(jù)接收等。

          2. Channel:基本上所有的IO在NIO中都從一個Channel開始。Channel有點像流,數(shù)據(jù)可以從channel到buffer,也可以從buffer到channel。

          3. Buffer:緩沖區(qū)本質(zhì)上是一個可以讀寫數(shù)據(jù)的內(nèi)存塊,可以理解成是一個容器對象(含數(shù)組),該對象提供了一組方法,可以更輕松的使用內(nèi)存塊,緩沖區(qū)對象內(nèi)置了一些機制,能夠跟蹤和記錄緩沖區(qū)的狀態(tài)變換情況,Channel提供從文件,網(wǎng)絡(luò)讀取數(shù)據(jù)的渠道,但是讀取或者寫入的數(shù)據(jù)都必須經(jīng)由Buffer。

          channel和buffer有好幾種類型。下面是Java NIO中的一些主要channel的實現(xiàn):

          FileChannel
          DatagramChannel
          SocketChannel
          ServerSocketChannel

          正如你所看到的,這些通道涵蓋了UDP和TCP網(wǎng)絡(luò)IO,以及文件IO。以下是Java NIO里關(guān)鍵的buffer實現(xiàn):

          ByteBuffer
          CharBuffer
          FloatBuffer
          IntBuffer
          LongBuffer
          ShortBuffer

          在微服務(wù)階段,一個請求可能涉及到多個不同服務(wù)之間的跨服務(wù)器調(diào)用,如果你想實現(xiàn)高性能的PRC框架來進行數(shù)據(jù)傳輸,那就可以基于Java NIO做個支持長連接、自定義協(xié)議、高并發(fā)的框架,比如Netty。Netty本身就是一個基于NIO的網(wǎng)絡(luò)框架, 封裝了Java NIO那些復(fù)雜的底層細節(jié),給你提供簡單好用的抽象概念來編程。比如Dubbo底層就是用的Netty。

          Netty通訊模式

          3.3 AIO

          AIO是異步非阻塞IO,相比NIO更進一步,進程讀取數(shù)據(jù)時只負責(zé)發(fā)送跟接收指令,數(shù)據(jù)的準(zhǔn)備工作完全由操作系統(tǒng)來處理。

          4 參考

          1. IO說:https://blog.csdn.net/u013177446/article/details/65936341

          2. 爆贊TCP講解:https://b23.tv/tMxwQV

          3. 通俗說IO:https://www.cnblogs.com/LBSer/p/4622749.html

          4. 小仙IO:https://t.1yb.co/iEAW


          推薦閱讀:

          數(shù)據(jù)庫系統(tǒng)設(shè)計概述

          Kafka原理篇:圖解kakfa架構(gòu)原理

          架構(gòu)設(shè)計方法論

          從面試角度一文學(xué)完 Kafka

          數(shù)據(jù)庫跟緩存的雙寫一致性

          全網(wǎng)最詳盡的負載均衡原理圖解


          關(guān)互聯(lián)網(wǎng)全棧架構(gòu)

              



          瀏覽 71
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本无码一区二区三区 | 操逼操逼操逼操逼操逼操逼操逼操逼 | 伊人大香在线 | 色婷婷中文在线 | 豆花无码成人免费视频 |