博客 / 詳情

返回

【後端面經-Java】I/O多路複用 簡錄

0. Java 線程IO模型

Java當中的線程I/O模型如圖所示:

1. BIO

當一個線程進行I/O操作的時候,傳統的做法是阻塞等待,直到I/O操作完成再繼續後續的操作,這種IO方式就是BIO(Blocking I/O)。

BIO方式的缺點是:

  • 大量併發線程的場景下效率過低;
  • 空等待浪費資源;

2. NIO

JDK1.4引入了NIO(No Blocking I/O或者是New I/O)。NIO是一種同步非阻塞的I/O模型,相對於BIO,NIO允許一個線程在I/O操作的時候處理其他任務,但是需要定期輪詢檢查I/O操作是否完成。
NIO的缺點在於:

  • 輪詢的時間間隔不好把握;
  • 一個線程處理一個I/O操作,如果存在大量I/O,處理其他任務和輪詢操作反覆切換狀態,上下文切換開銷大;

3. I/O多路複用(主要)

3.1 概念

為了解決NIO的缺點,Linux引入了I/O多路複用的機制,即一個線程可以同時監聽多個I/O操作,當某個I/O操作完成後,會通知線程進行處理。
多路指的是多個SOCKET連接之間的I/O操作,複用指的是共用一個線程。
I/O多路複用的優點在於:

  • 一個線程可以同時監聽多個I/O操作,減少了線程的數量,避免了線程切換的開銷;

需要注意的是,I/O多路複用只有和NIO配合使用才能發揮作用,因為NIO是非阻塞的,所以可以在一個線程中同時監聽多個I/O操作,而BIO是阻塞的,一個線程只能處理一個I/O操作,所以無法實現I/O多路複用。

3.2 實現

I/O多路複用的實現思路:
對於多個socket連接,程序提供一個文件描述符集合給系統,當某個接口的I/O操作完成後,會通知線程進行處理。

實現I/O多路複用的方式有三種:selectpollepoll

1. select

函數原型如下所示:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds:文件描述符的數量,即文件描述符集合中最大的文件描述符加1;
  • readfds:讀文件描述符集合;
  • writefds:寫文件描述符集合;
  • exceptfds:異常文件描述符集合;
  • timeout:超時時間;

從參數可以看出來select方式監聽讀、寫、異常事件。

select根據監聽的事件類型分別創建三個文件描述符數組,然後在timeout時間內阻塞線程進行監聽,直到有事件發生或者超時。之後檢查數組中是否有事件到達。
select的缺點在於:

  • 文件描述符數組大小有限,為1024,因此對於高併發場景並不適用;
  • 維持三個文件描述符數組,佔據大量的內存空間;
  • 每次調用select需要將數組從用户空間拷貝到內核空間,同時重新對數組進行遍歷查找,效率低;

2. poll

函數原型如下所示:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • fds:文件描述符數組;
  • ndfs:文件描述符數組的大小;
  • timeout:超時時間;

本質的工作過程和select類似,但是稍微做了改進,只需要構建一個數組,並且數組大小不受限制,而是能夠自由指定;
poll的缺點在於:

  • 每次調用poll之後都需要進行數組遍歷,這一點並沒有改進

3. epoll

為了解決selectpoll的缺點,在高併發場景下,不同的操作系統引入了不同的解決方案,例如Linux引入了epoll、FreeBSD引入了kqueue、Solaris引入了/dev/poll
epoll實現I/O多路複用,步驟如下:

  1. 先創建epoll對象:

    int epfd = epoll_create(10);

    其中,int epoll_create(int size)會在內核空間開闢一塊指定大小的數據表,並由epfd指向這部分內存。

  2. 創建好epoll對象之後,使用epoll_ctl將註冊需要監聽的事件:

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  3. epfd是創建數組之後的內存指針;
  4. op是操作類型,包括三種模式:

    • EPOLL_CTL_ADD:添加需要監聽的事件;
    • EPOLL_CTL_MOD:修改需要監聽的事件;
    • EPOLL_CTL_DEL:刪除需要監聽的事件;
  5. fd是需要監聽的文件描述符,需要支持NIO;
  6. event記錄了註冊事件的具體信息。數據結構如下所示:

    typedef union epoll_data {
     void    *ptr;
     int      fd;
     uint32_t u32;
     uint64_t u64;
    } epoll_data_t;
    
    struct epoll_event {
     uint32_t     events;    /* Epoll events */
     epoll_data_t data;      /* User data variable */
    };
  7. 使用epoll_wait進行監聽:
    epoll_wait函數原型如下所示:

    int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
  8. epfd是創建數組之後的內存指針;
  9. evlist是用於存放事件的數組,也是返回的結果數組,包含被觸發事件的對應文件描述符;

    • 這裏顯示了和selectpoll的區別,selectpoll會返回所有文件描述符然後遍歷,而epoll只會返回被觸發事件的文件描述符;
  10. maxevents是監聽事件的最大容量;
  11. timeout是超時時間;
    監聽步驟是block的,也就是阻塞的,只有超時才會返回;

epoll的優點在於:

  • 只返回觸發事件的文件描述符,避免了整個數組的遍歷;
  • 支持水平觸發(Level Trigger)和邊緣觸發(Edge Trigger)兩種模式;

    • 對於水平觸發和邊緣觸發,具體解釋可參考這篇博客;

      4. AIO

      AIO(Asynchronous I/O),即異步非阻塞I/O模型,AIO的實現方式是基於事件和回調機制的,當一個I/O操作完成後,會通知線程進行處理,因此不需要輪詢操作。

AIO和NIO的區別在於:

  • NIO:線程需要定時檢查I/O操作是否完成;
  • AIO:安心去做其他事情,等到通知之後才會進行處理;

5. 技術對比

5.1 BIO、NIO、I/O多路複用、AIO對比

5.2 selectpollepoll對比

6. 面試模擬

Q:IO多路複用是什麼意思?
A:IO多路複用指的是一個線程管理多個IO連接,監聽多個IO事件;

Q:NIO的具體含義
A:NIO一般理解為Not Blocking IO,即非阻塞IO,和傳統的BIO(阻塞IO)相比,NIO模型中,一個線程在IO操作的時候可以處理其他任務,定期輪詢檢查IO操作是否完成

Q:基於什麼實現的I/O多路複用?
A:傳統的實現方式包括selectpoll,但是這兩類方法都需要遍歷數組,效率較低,為此不同的操作系統提出了不同的改進方案,例如solaris提出了/dev/poll,FreeBSD提出了kqueue,Linux提出了epoll,而epoll相比於selectpoll的主要區別就是返回的事件列表只包括觸發事件的文件描述符,而不是全部監聽事件的文件描述符,改進了數組遍歷這一監聽方式。

參考資料

  1. 一文徹底理解Java IO模型(阻塞IO非阻塞IO/IO多路複用)
  2. IO多路複用機制詳解
  3. 講講BIO和NIO以及IO多路複用
user avatar fish_60c2dbded700f 頭像 markerhub 頭像 codingdgsun 頭像 chazhoudeqingchun 頭像 redorblack 頭像 biubiubiu_5ea3ee0e6b5fd 頭像 goudantiezhuerzi 頭像 buxiyan 頭像 micherwa 頭像 dataanalysis 頭像 zhongganqingdejinzhengu_cbvxch 頭像
11 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.