1. 小白也看得懂的 I/O 多路復(fù)用解析

        共 7775字,需瀏覽 16分鐘

         ·

        2022-05-11 09:35

        前言

        IO多路復(fù)用目前在大廠的面試中,一般在兩個(gè)地方可能會(huì)被問(wèn)到,一個(gè)是在問(wèn)到網(wǎng)絡(luò)這一塊的時(shí)候,另一個(gè)是在問(wèn)到 Redis 這一塊的時(shí)候,因?yàn)?Redis 底層也是使用了IO多路復(fù)用,所以整體來(lái)說(shuō) IO多路復(fù)用,也算是一道比較高頻的一個(gè)面試題,所以今天跟大家來(lái)分享一下。


        本文內(nèi)容有視頻版本,喜歡看視頻的同學(xué)可以直接通過(guò)下面的二維碼觀看。如果你對(duì)文章的內(nèi)容有疑惑,可以先看視頻的對(duì)應(yīng)內(nèi)容,視頻可能講的會(huì)更細(xì)一點(diǎn)。

        基礎(chǔ)概念

        首先我們了解下2個(gè)基礎(chǔ)概念,這2個(gè)概念在后續(xù)的文章中會(huì)反復(fù)用到。


        Socket

        套接字。百科:對(duì)網(wǎng)絡(luò)中不同主機(jī)上的應(yīng)用進(jìn)程之間進(jìn)行雙向通信的端點(diǎn)的抽象。

        例子1:客戶端將數(shù)據(jù)通過(guò)網(wǎng)線發(fā)送到服務(wù)端,客戶端發(fā)送數(shù)據(jù)需要一個(gè)出口,服務(wù)端接收數(shù)據(jù)需要一個(gè)入口,這兩個(gè)“口子”就是 Socket。

        例子2:兩個(gè)人通過(guò)電話進(jìn)行通信,兩個(gè)人都需要持有1個(gè)電話,socket 就類似于這個(gè)電話。


        FD:file descriptor

        文件描述符,非負(fù)整數(shù)。“一切皆文件”,linux 中的一切資源都可以通過(guò)文件的方式訪問(wèn)和管理。而 FD 就類似文件的索引(符號(hào)、指針),指向某個(gè)資源,內(nèi)核(kernel)利用 FD 來(lái)訪問(wèn)和管理資源。


        之前在視頻中有同學(xué)問(wèn)既然有 socket,為什么文章內(nèi)容全是用的 FD 來(lái)舉例,這是因?yàn)楫?dāng)我們調(diào)用內(nèi)核函數(shù)創(chuàng)建 socket 后,內(nèi)核返回給我們的是 socket 對(duì)應(yīng)的文件描述符(fd),所以我們對(duì) socket 的操作基本都是通過(guò) fd 來(lái)進(jìn)行。


        Socket 通信

        接著我們通過(guò)一張圖來(lái)看下客戶端和服務(wù)器使用 socket 進(jìn)行通信的核心流程。

        圖中函數(shù)的含義如下:

        socket:創(chuàng)建一個(gè)套接字

        bind:將 socket 綁定到指定地址

        listen:使套接字處于監(jiān)聽(tīng)狀態(tài),等待客戶端連接到來(lái)

        accept:接受客戶端連接

        connect:客戶端發(fā)起連接

        read:從 fd 對(duì)應(yīng)的 socket 中讀取數(shù)據(jù)

        write:將數(shù)據(jù)寫(xiě)入 fd 對(duì)應(yīng)的 socket 中

        close:關(guān)閉 socket 文件描述符

        核心交互流程如下:

        1)服務(wù)器端通過(guò) socket、bind、listen 對(duì) socket 進(jìn)行初始化,最后阻塞在 accept 等待客戶端請(qǐng)求到來(lái)。

        2)客戶端通過(guò) socket 進(jìn)行初始化,然后使用 connect 向服務(wù)端發(fā)起連接請(qǐng)求。此時(shí)客戶端會(huì)和服務(wù)端進(jìn)行 TCP 三次握手,三次握手完成后,客戶端和服務(wù)端建立連接完畢,開(kāi)始進(jìn)入數(shù)據(jù)傳輸過(guò)程。

        3)客戶端發(fā)起 write 系統(tǒng)調(diào)用寫(xiě)入數(shù)據(jù),數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間 socket 緩沖區(qū),最后內(nèi)核將數(shù)據(jù)通過(guò)網(wǎng)絡(luò)發(fā)送到服務(wù)器。

        4)數(shù)據(jù)經(jīng)過(guò)網(wǎng)絡(luò)傳輸?shù)竭_(dá)服務(wù)器網(wǎng)卡,接著內(nèi)核將數(shù)據(jù)拷貝到對(duì)應(yīng)的 socket 接收隊(duì)列,最后將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間。

        5)客戶端和服務(wù)器完成交互后,調(diào)用 close 函數(shù)來(lái)斷開(kāi)連接。


        IO模型小例子

        接著我們通過(guò)一個(gè)例子來(lái)了解下各種IO模型。

        例子:你是一個(gè)老師,讓學(xué)生做作業(yè),學(xué)生做完作業(yè)后收作業(yè)。

        同步阻塞:逐個(gè)收作業(yè),先收A,再收B,接著是C、D,如果有一個(gè)學(xué)生還未做完,則你會(huì)等到他寫(xiě)完,然后才繼續(xù)收下一個(gè)。

        解析:這就是同步阻塞的特點(diǎn),只要中間有一個(gè)未就緒,則你會(huì)被阻塞住,從而影響到后面的其他學(xué)生。

        同步非阻塞:逐個(gè)收作業(yè),先收A,再收B,接著是C、D,如果有一個(gè)學(xué)生還未做完,則你會(huì)跳過(guò)該學(xué)生,繼續(xù)去收下一個(gè)。

        解析:可以看到同步非阻塞相較于同步阻塞已經(jīng)是更好的方案了,你不會(huì)因?yàn)槟硞€(gè)學(xué)生未就緒而阻塞住,這樣就可以減少對(duì)后續(xù)學(xué)生的影響。但是這個(gè)方案也可能會(huì)出現(xiàn)其他問(wèn)題,如果你下去收作業(yè)的時(shí)候,全部學(xué)生都還沒(méi)做完,則你可能會(huì)白走一圈,然后一個(gè)作業(yè)也沒(méi)收到。

        select/poll:學(xué)生寫(xiě)完了作業(yè)會(huì)舉手,但是你不知道是誰(shuí)舉手,需要一個(gè)個(gè)的去詢問(wèn)。

        解析:這個(gè)方案相較于同步非阻塞來(lái)說(shuō)有一點(diǎn)好處,就是你是確認(rèn)有學(xué)生做完的,所以你下去肯定能收到作業(yè),但是他有一個(gè)不好的點(diǎn)在于你需要一個(gè)個(gè)的去詢問(wèn)。

        epoll:學(xué)生寫(xiě)完了作業(yè)會(huì)舉手,你知道是誰(shuí)舉手,你直接去收作業(yè)。

        解析:這個(gè)方案就很高效了,每次都能準(zhǔn)確的收到作業(yè)。


        同步阻塞IO

        核心流程:當(dāng)應(yīng)用程序發(fā)起 read 系統(tǒng)調(diào)用時(shí),在內(nèi)核數(shù)據(jù)沒(méi)有準(zhǔn)備好之前,應(yīng)用程序會(huì)一直處于阻塞等待狀態(tài),直到內(nèi)核把數(shù)據(jù)準(zhǔn)備好了返回給應(yīng)用程序


        交互流程

        我們通過(guò)兩段代碼的一個(gè)動(dòng)圖來(lái)模擬同步阻塞IO下服務(wù)端和客戶端的執(zhí)行流程:


        大致流程如下:

        1)服務(wù)端進(jìn)行初始化:新建 socket、綁定地址、轉(zhuǎn)為服務(wù)端 socket

        2)服務(wù)端調(diào)用 accept,進(jìn)入阻塞狀態(tài),等待客戶端連接

        3)客戶端新建 socket,向服務(wù)端發(fā)起連接

        4)服務(wù)端和客戶端通過(guò) TCP 三次握手建立連接

        5)服務(wù)端繼續(xù)執(zhí)行 read 函數(shù),進(jìn)入阻塞狀態(tài),等待客戶端發(fā)送數(shù)據(jù)

        6)客戶端向服務(wù)端發(fā)送數(shù)據(jù)

        7)服務(wù)端讀取數(shù)據(jù),執(zhí)行邏輯處理


        同步阻塞IO模型

        我們通過(guò) read 函數(shù)來(lái)看下服務(wù)器內(nèi)部用戶空間和內(nèi)核空間的調(diào)用流程,如下圖所示:

        大致流程如下:

        1)應(yīng)用進(jìn)程發(fā)起 read 系統(tǒng)調(diào)用

        2)應(yīng)用進(jìn)程阻塞等待數(shù)據(jù)就緒

        3)數(shù)據(jù)通過(guò)網(wǎng)絡(luò)傳輸?shù)竭_(dá)網(wǎng)卡,然后再到內(nèi)核socket緩沖區(qū),當(dāng)數(shù)據(jù)被拷貝到內(nèi)核 socket 緩沖區(qū)時(shí),此時(shí)處于就緒狀態(tài)

        4)將數(shù)據(jù)從內(nèi)核拷貝到應(yīng)用程序緩沖區(qū),返回成功


        多線程版本:文中使用的例子是單線程,如果是多線程則在每個(gè) socket 建立連接后新建線程去負(fù)責(zé)處理該 socket 后續(xù)的流程,這樣就不會(huì)由于單個(gè) socket 阻塞住而影響到其他 socket。


        總結(jié)

        單線程:某個(gè) socket 阻塞,會(huì)影響到其他 socket 處理。

        多線程:當(dāng)客戶端較多時(shí),會(huì)造成資源浪費(fèi),全部 socket 中可能每個(gè)時(shí)刻只有幾個(gè)就緒。同時(shí),線程的調(diào)度、上下文切換乃至它們占用的內(nèi)存,可能都會(huì)成為瓶頸。


        同步非阻塞IO

        核心流程:當(dāng)應(yīng)用程序發(fā)起 read 系統(tǒng)調(diào)用時(shí),在內(nèi)核數(shù)據(jù)沒(méi)有準(zhǔn)備好之前,內(nèi)核會(huì)直接返回錯(cuò)誤,應(yīng)用程序不斷輪詢內(nèi)核,直到內(nèi)核把數(shù)據(jù)準(zhǔn)備好了返回給應(yīng)用程序。


        交互流程

        我們通過(guò)兩段代碼的一個(gè)動(dòng)圖來(lái)模擬同步阻塞IO下服務(wù)端和客戶端的執(zhí)行流程:


        大致流程如下:

        1)服務(wù)端調(diào)用 accept,數(shù)據(jù)未就緒,內(nèi)核返回-1

        2)服務(wù)端調(diào)用 accept,數(shù)據(jù)未就緒,內(nèi)核返回-1

        3)服務(wù)端調(diào)用 accept,數(shù)據(jù)未就緒,內(nèi)核返回-1

        4)客戶端新建 socket,向服務(wù)端發(fā)起連接

        4)服務(wù)端調(diào)用 accept,服務(wù)端和客戶端通過(guò) TCP 三次握手建立連接

        5)服務(wù)端執(zhí)行后續(xù)邏輯處理


        我們通過(guò) read 函數(shù)來(lái)看下服務(wù)器內(nèi)部用戶空間和內(nèi)核空間的調(diào)用流程,如下圖所示:

        大致流程如下:

        1)服務(wù)端調(diào)用 read,數(shù)據(jù)未就緒,內(nèi)核返回-1

        2)服務(wù)端調(diào)用 read,數(shù)據(jù)未就緒,內(nèi)核返回-1

        3)服務(wù)端調(diào)用?read,數(shù)據(jù)就緒

        4)將數(shù)據(jù)從內(nèi)核拷貝到應(yīng)用程序緩沖區(qū),返回成功


        同步非阻塞IO模型


        總結(jié):提供了非阻塞調(diào)用的方式,從操作系統(tǒng)層面解決了阻塞問(wèn)題。

        優(yōu)點(diǎn):單個(gè) socket 阻塞,不會(huì)影響到其他 socket?

        缺點(diǎn):需要不斷的遍歷進(jìn)行系統(tǒng)調(diào)用,有一定開(kāi)銷


        SELECT

        核心流程:

        1)應(yīng)用程序首先發(fā)起 select 系統(tǒng)調(diào)用,傳入要監(jiān)聽(tīng)的文件描述符集合

        2)內(nèi)核遍歷應(yīng)用程序傳入的?fd 集合,如果遍歷完一遍后發(fā)現(xiàn)沒(méi)有就緒的 fd 則用戶進(jìn)程會(huì)進(jìn)入阻塞狀態(tài),如果有就緒的 fd 則會(huì)對(duì)就緒的 fd 打標(biāo),然后返回

        3)應(yīng)用程序遍歷 fd 集合,找到就緒的 fd,進(jìn)行相應(yīng)的事件處理


        select 接口

        /** * 獲取就緒事件 * * @param nfds      3個(gè)監(jiān)聽(tīng)集合的文件描述符最大值+1 * @param readfds   要監(jiān)聽(tīng)的可讀文件描述符集合 * @param writefds  要監(jiān)聽(tīng)的可寫(xiě)文件描述符集合 * @param exceptfds 要監(jiān)聽(tīng)的異常文件描述符集合 * @param timeval   本次調(diào)用的超時(shí)時(shí)間 * @return 大于0:已就緒的文件描述符數(shù);等于0:超時(shí);小于:出錯(cuò) */int select(int nfds,           fd_set *readfds,           fd_set *writefds,           fd_set *exceptfds,           struct timeval *timeout);
        ?


        交互流程

        我們通過(guò)一個(gè)動(dòng)圖來(lái)模擬服務(wù)器內(nèi)部用戶空間和內(nèi)核空間的調(diào)用流程,如下圖所示:

        大致流程如下:

        1)用戶空間發(fā)起 select 系統(tǒng)調(diào)用,將監(jiān)聽(tīng)的 fd 集合從用戶空間拷貝到內(nèi)核空間

        2)內(nèi)核遍歷 fd 集合,檢查數(shù)據(jù)是否就緒

        3)如果遍歷一遍后發(fā)現(xiàn)沒(méi)有 fd 就緒,則會(huì)將當(dāng)前用戶進(jìn)程阻塞,讓出 CPU 給其他進(jìn)程

        4)當(dāng)客戶端將數(shù)據(jù)發(fā)送到服務(wù)端,進(jìn)入內(nèi)核后,會(huì)通過(guò)數(shù)據(jù)庫(kù)包找到對(duì)應(yīng)的socket?

        PS:客戶端發(fā)送數(shù)據(jù)到數(shù)據(jù)進(jìn)入服務(wù)端內(nèi)核的流程類似下面 epoll 的流程

        5)socket 檢查是否有阻塞等待的進(jìn)程,如果有則喚醒該進(jìn)程

        6)用戶進(jìn)程恢復(fù)運(yùn)行后,會(huì)再遍歷 fd?集合進(jìn)行檢查,此時(shí)它會(huì)檢查到某些 fd 已經(jīng)就緒了,它會(huì)給這些 fd 打上標(biāo)記,然后結(jié)束阻塞,返回到用戶空間

        7)用戶空間知道有事件就緒,遍歷 fd 集合,找到就緒的 fd,進(jìn)行相應(yīng)的事件處理,例如將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到應(yīng)用程序緩沖區(qū)

        8)最后執(zhí)行邏輯處理。


        IO多路復(fù)用模型


        fd_set

        fd_set 在 select 的整個(gè)調(diào)用過(guò)程中表達(dá)了兩種不同的意思。

        在入?yún)r(shí),fd_set 表示應(yīng)用程序要監(jiān)聽(tīng)哪些 fd;在回參時(shí),fd_set表示哪些 fd 已經(jīng)就緒了。

        應(yīng)用程序傳入的 fd_set 其實(shí)是個(gè)位圖,例如我們要監(jiān)聽(tīng) fd = 1、fd = 4,則傳入 0000 0101,也就是 5。

        這邊使用的 long 類型數(shù)組來(lái)實(shí)現(xiàn)位圖:1個(gè) long 可以表示64位,則16個(gè)long可以表示1024位。

        當(dāng)內(nèi)核處理完畢,將就緒的 fd 返回時(shí),會(huì)將就緒的 fd 對(duì)應(yīng)的位標(biāo)記為1,然后覆蓋掉入?yún)⒌?fd_set,所以我們最終返回時(shí)的 fd_set 表示的是哪些 fd 是就緒的。


        總結(jié)

        • 將 socket 是否就緒檢查邏輯下沉到操作系統(tǒng)層面,避免大量系統(tǒng)調(diào)用。

        • 告訴你有事件就緒,但是沒(méi)告訴你具體是哪個(gè) FD。


        優(yōu)點(diǎn)

        • 不需要每個(gè) FD 都進(jìn)行一次系統(tǒng)調(diào)用,解決了頻繁的用戶態(tài)內(nèi)核態(tài)切換問(wèn)題


        缺點(diǎn)

        • 單進(jìn)程監(jiān)聽(tīng)的 FD 存在限制,默認(rèn)1024

        • 每次調(diào)用需要將 FD 從用戶態(tài)拷貝到內(nèi)核態(tài)

        • 不知道具體是哪個(gè)文件描述符就緒,需要遍歷全部文件描述符

        • 入?yún)⒌?個(gè) fd_set 集合每次調(diào)用都需要重置



        POLL

        核心流程:基本同 select。

        poll 接口

        /** * 獲取就緒事件 * * @param pollfd  要監(jiān)聽(tīng)的文件描述符集合 * @param nfds    文件描述符數(shù)量 * @param timeout 本次調(diào)用的超時(shí)時(shí)間 * @return 大于0:已就緒的文件描述符數(shù);等于0:超時(shí);小于:出錯(cuò) */int poll(struct pollfd *fds,         unsigned int nfds,         int timeout);
        struct pollfd { int fd; // 監(jiān)聽(tīng)的文件描述符 short events; // 監(jiān)聽(tīng)的事件 short revents; // 就緒的事件}
        ?

        poll 函數(shù)基本同 select,只是對(duì) select 進(jìn)行了一些小優(yōu)化,一個(gè)是優(yōu)化了1024個(gè)文件描述符上限,另一個(gè)是新定義了 pollfd 數(shù)據(jù)結(jié)構(gòu),使用兩個(gè)不同的變量來(lái)表示監(jiān)聽(tīng)的事件和就緒的事件,這樣就不需要像 select 那樣每次重置 fd_set 了。


        總結(jié)跟 select 基本類似,主要優(yōu)化了監(jiān)聽(tīng)1024的限制。

        優(yōu)點(diǎn)

        • 不需要每個(gè) FD 都進(jìn)行一次系統(tǒng)調(diào)用,導(dǎo)致頻繁的用戶態(tài)內(nèi)核態(tài)切換

        缺點(diǎn)

        • 每次需要將 FD 從用戶態(tài)拷貝到內(nèi)核態(tài)

        • 不知道具體是哪個(gè)文件描述符就緒,需要遍歷全部文件描述符


        EPOLL

        核心流程:

        1)應(yīng)用程序調(diào)用 epoll_create,內(nèi)核會(huì)分配一塊內(nèi)存空間,創(chuàng)建一個(gè) epoll,最后將 epoll 的 fd 返回,我們后續(xù)可以通過(guò)這個(gè) fd 來(lái)操作 epoll 對(duì)象

        2)應(yīng)用程序不斷調(diào)用 epoll_ctl 將我們要監(jiān)聽(tīng)的 fd 維護(hù)到 epoll,內(nèi)核通過(guò)紅黑樹(shù)的結(jié)構(gòu)來(lái)高效的維護(hù)我們傳入的 fd 集合

        3)應(yīng)用程序調(diào)用?epoll_wait 來(lái)獲取就緒事件,內(nèi)核檢查 epoll 的就緒列表,如果就緒列表為空則會(huì)進(jìn)入阻塞,否則直接返回就緒的事件。

        4)應(yīng)用程序根據(jù)內(nèi)核返回的就緒事件,進(jìn)行相應(yīng)的事件處理


        epoll 接口

        /** * 創(chuàng)建一個(gè)epoll * * @param size epoll要監(jiān)聽(tīng)的文件描述符數(shù)量 * @return epoll的文件描述符 */int epoll_create(int size);
        /** * 事件注冊(cè) * * @param epfd epoll的文件描述符,epoll_create創(chuàng)建時(shí)返回 * @param op 操作類型:新增(1)、刪除(2)、更新(3) * @param fd 本次要操作的文件描述符 * @param epoll_event 需要監(jiān)聽(tīng)的事件:讀事件、寫(xiě)事件等 * @return 如果調(diào)用成功返回0, 不成功返回-1 */int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
        /** * 獲取就緒事件 * * @param epfd epoll的文件描述符,epoll_create創(chuàng)建時(shí)返回 * @param events 用于回傳就緒的事件 * @param maxevents 每次能處理的最大事件數(shù) * @param timeout 等待I/O事件發(fā)生的超時(shí)時(shí)間,-1相當(dāng)于阻塞,0相當(dāng)于非阻塞 * @return 大于0:已就緒的文件描述符數(shù);等于0:超時(shí);小于:出錯(cuò) */int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
        ?


        交互流程

        我們通過(guò)一個(gè)動(dòng)圖來(lái)模擬服務(wù)器內(nèi)部用戶空間和內(nèi)核空間的調(diào)用流程,如下圖所示:

        大致流程如下:

        1)用戶空間調(diào)用?epoll_create?,內(nèi)核新建 epoll 對(duì)象,返回 epoll 的 fd,用于后續(xù)操作

        2)用戶空間反復(fù)調(diào)用 epoll_ctl 將我們要監(jiān)聽(tīng)的 fd 維護(hù)到 epoll,底層通過(guò)紅黑樹(shù)來(lái)高效的維護(hù) fd 集合

        3)用戶空間調(diào)用 epoll_wait 獲取就緒事件,內(nèi)核檢查 epoll 的就緒列表,如果就緒列表為空則會(huì)進(jìn)入阻塞

        4)客戶端向服務(wù)端發(fā)送數(shù)據(jù),數(shù)據(jù)通過(guò)網(wǎng)絡(luò)傳輸?shù)椒?wù)端的網(wǎng)卡

        5)網(wǎng)卡通過(guò)?DMA 的方式將數(shù)據(jù)包寫(xiě)入到指定內(nèi)存中(ring_buffer),處理完成后通過(guò)中斷信號(hào)告訴 CPU 有新的數(shù)據(jù)包到達(dá)

        6)CPU 收到中斷信號(hào)后,進(jìn)行響應(yīng)中斷,首先保存當(dāng)前執(zhí)行程序的上下文環(huán)境,然后調(diào)用中斷處理程序(網(wǎng)卡驅(qū)動(dòng)程序)進(jìn)行處理:

        • 根據(jù)數(shù)據(jù)包的ip和port找到對(duì)應(yīng)的socket,將數(shù)據(jù)放到socket的接收隊(duì)列;

        • 執(zhí)行 socket 對(duì)應(yīng)的回調(diào)函數(shù):將當(dāng)前 socket 添加到 eventpoll 的就緒列表、喚醒 eventpool 等待隊(duì)列里的用戶進(jìn)程(設(shè)置為RUNNING狀態(tài))

        7)用戶進(jìn)程恢復(fù)運(yùn)行后,檢查 eventpoll 里的就緒列表不為空,則將就緒事件填充到入?yún)⒅械?events 里,然后返回

        8)用戶進(jìn)程收到返回的事件后,執(zhí)行 events 里的事件處理,例如讀事件則將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到應(yīng)用程序緩沖區(qū)

        9)最后執(zhí)行邏輯處理。


        IO多路復(fù)用模型

        同 select。


        總結(jié)

        epoll 直接將 fd 集合維護(hù)在內(nèi)核中,通過(guò)紅黑樹(shù)來(lái)高效管理 fd 集合,同時(shí)維護(hù)一個(gè)就緒列表,當(dāng) fd 就緒后會(huì)添加到就緒列表中,當(dāng)應(yīng)用空間調(diào)用 epoll_wait 獲取就緒事件時(shí),內(nèi)核直接判斷就緒列表即可知道是否有事件就緒。


        優(yōu)點(diǎn)

        解決了 select 和 poll 的缺點(diǎn),高效處理高并發(fā)下的大量連接,同時(shí)有非常優(yōu)異的性能。


        缺點(diǎn)

        • 跨平臺(tái)性不夠好,只支持 linux,macOS 等操作系統(tǒng)不支持

        • 相較于 epoll,select 更輕量可移植性更強(qiáng)

        • 在監(jiān)聽(tīng)連接數(shù)和事件較少的場(chǎng)景下,select 可能更優(yōu)


        LT?VS ET

        LT:Level-triggered,水平(條件)觸發(fā),默認(rèn)。epoll_wait 檢測(cè)到事件后,如果該事件沒(méi)被處理完畢,后續(xù)每次 epoll_wait 調(diào)用都會(huì)返回該事件。

        ET:Edge-triggered,邊緣觸發(fā)。epoll_wait 檢測(cè)到事件后,只會(huì)在當(dāng)次返回該事件,不管該事件是否被處理完畢。

        小結(jié)

        epoll 和 select、poll 默認(rèn)都是 LT 模式,LT 模式會(huì)更安全一點(diǎn),而 ET 則是 epoll 為了性能開(kāi)發(fā)的一種新模式,LT 模式下內(nèi)核在返回就緒事件之前都會(huì)進(jìn)行一次額外的判斷,如果 fd 量較大,會(huì)有一定的性能損耗。


        總結(jié)

        可以看到從最初的同步阻塞IO,到現(xiàn)在主流的 epoll,其實(shí)是一個(gè)不斷演進(jìn)的過(guò)程,就像我們的業(yè)務(wù)系統(tǒng)一樣。

        同步阻塞IO的方式實(shí)現(xiàn)比較簡(jiǎn)單,同時(shí)在當(dāng)時(shí)可能已經(jīng)能滿足需求了,因此被最早提出來(lái),然后隨著不斷的發(fā)展,在一些場(chǎng)景下,同步阻塞IO逐漸不能滿足需求,于是操作系統(tǒng)底層開(kāi)始優(yōu)化,提出了非阻塞的模式。類似的,同步非阻塞IO也存在一定的問(wèn)題,于是就有了后續(xù)的IO多路復(fù)用。

        現(xiàn)在還有一種更牛逼的IO模型也在發(fā)展,叫做異步IO,這種模型下,你只需要一次非阻塞的系統(tǒng)調(diào)用,后續(xù)的事情全部由內(nèi)核來(lái)幫你完成。不過(guò)異步IO當(dāng)前在 linux 下還不夠完善,所以當(dāng)前 linux 的主流還是 epoll。


        推薦閱讀

        全網(wǎng)最實(shí)用的 IDEA Debug 調(diào)試技巧(超詳細(xì)案例)

        百萬(wàn)級(jí)QPS,支撐淘寶雙11需要哪些技術(shù)

        面試官:如何進(jìn)行 JVM 調(diào)優(yōu)(附真實(shí)案例)

        Java 基礎(chǔ)高頻面試題(2021年最新版)

        Java 集合框架高頻面試題(2021年最新版)

        面試必問(wèn)的 Spring,你懂了嗎?

        面試必問(wèn)的 MySQL,你懂了嗎?

        最近我將面試:阿里、字節(jié)、美團(tuán)、快手、拼多多等大廠的高頻面試整理出來(lái),并按大廠的標(biāo)準(zhǔn)給出自己的解析。

        群里有不少同學(xué)看完拿下了阿里、美團(tuán)等大廠 Offer,希望能助你一臂之力,早日拿下大廠 Offer。

        獲取方式:關(guān)注公眾號(hào)回復(fù)【面試】即可領(lǐng)取,更多大廠面試真題解析 PDF 整理中。

        瀏覽 173
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 高清国产一级婬片A片大黄九色 | 国产吧在线视频 | 草永久欧美第一页 | 久久a70| 国产精品s色 |