偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

IO模型 Select、Poll、Epoll,你知道哪個(gè)?

存儲(chǔ) 存儲(chǔ)架構(gòu)
應(yīng)用只需要向內(nèi)核發(fā)送一個(gè)讀取請(qǐng)求,告訴內(nèi)核它要讀取數(shù)據(jù)后即刻返回;內(nèi)核收到請(qǐng)求后會(huì)建立一個(gè)信號(hào)聯(lián)系,當(dāng)數(shù)據(jù)準(zhǔn)備就緒,內(nèi)核會(huì)主動(dòng)把數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間,等所有操作都完成之后,內(nèi)核會(huì)發(fā)起一個(gè)通知告訴應(yīng)用,我們稱這種模式為異步IO模型。

什么是IO?

IO中的I就是input,O就是output,IO模型即輸入輸出模型,而比較常聽(tīng)說(shuō)的便是磁盤(pán)IO,網(wǎng)絡(luò)IO。

什么是操作系統(tǒng)的IO?

我們?nèi)绻枰獙?duì)磁盤(pán)進(jìn)行讀取或者寫(xiě)入數(shù)據(jù)的時(shí)候必須得有主體去操作,這個(gè)主體就是應(yīng)用程序。應(yīng)用程序是不能直接進(jìn)行一些讀寫(xiě)操作(IO)的,因?yàn)橛脩艨赡軙?huì)利用此程序直接或者間接的對(duì)計(jì)算機(jī)造成破壞,只能交給底層軟件—操作系統(tǒng).也就是說(shuō)應(yīng)用程序想要對(duì)磁盤(pán)進(jìn)行讀取或者寫(xiě)入數(shù)據(jù),只能通過(guò)操作系統(tǒng)對(duì)上層開(kāi)放的API來(lái)進(jìn)行。在任何一個(gè)應(yīng)用程序里面,都會(huì)有進(jìn)程地址空間,該空間分為兩部分,一部分稱為用戶空間(允許應(yīng)用程序進(jìn)行訪問(wèn)的空間),另一部分稱為內(nèi)核空間(只能給操作系統(tǒng)進(jìn)行訪問(wèn)的空間,它受到保護(hù))。

應(yīng)用程序想要進(jìn)行一次IO操作分為兩個(gè)階段:

?IO調(diào)用:應(yīng)用程序進(jìn)程向操作系統(tǒng)內(nèi)核發(fā)起調(diào)用【1】。

?IO執(zhí)行:操作系統(tǒng)內(nèi)核完成IO操作【2】。

操作系統(tǒng)完成一次IO操作包括兩個(gè)過(guò)程:

?數(shù)據(jù)準(zhǔn)備階段:內(nèi)核等待I/O設(shè)備準(zhǔn)備好數(shù)據(jù)(從網(wǎng)卡copy到內(nèi)核緩沖區(qū)【3】。

?數(shù)據(jù)copy階段:將數(shù)據(jù)從內(nèi)核緩沖區(qū)copy到用戶進(jìn)程緩沖區(qū)【4】。

應(yīng)用程序一次I/O流程如下:

圖片圖片

一個(gè)完整的IO過(guò)程包括以下幾個(gè)步驟:

1.應(yīng)用程序進(jìn)程向操作系統(tǒng)發(fā)起IO調(diào)用請(qǐng)求。

2.操作系統(tǒng)準(zhǔn)備數(shù)據(jù),外部設(shè)備的數(shù)據(jù)通過(guò)網(wǎng)卡加載到內(nèi)核緩沖區(qū)。

3.操作系統(tǒng)拷貝數(shù)據(jù),即將內(nèi)核緩沖區(qū)的數(shù)據(jù)copy到用戶進(jìn)程緩沖區(qū)。

而一次IO的本質(zhì)其實(shí)就是: 等待 + 拷貝

IO模型有哪些?

1.阻塞式 IO:

服務(wù)端為了處理客戶端的連接和數(shù)據(jù)處理:

偽代碼具體如下:

listenfd = socket();   // 打開(kāi)一個(gè)網(wǎng)絡(luò)通信套接字
bind(listenfd);        // 綁定
listen(listenfd);      // 監(jiān)聽(tīng)
while(true) {
  buf = new buf[1024]; // 讀取數(shù)據(jù)容器
  connfd = accept(listenfd);  // 阻塞 等待建立連接
  int n = read(connfd, buf);  // 阻塞 讀數(shù)據(jù)
  doSomeThing(buf);  // 處理數(shù)據(jù)
  close(connfd);     // 關(guān)閉連接
}

上面的偽代碼中我們可以看出,服務(wù)端處理客戶端的請(qǐng)求阻塞在兩個(gè)地方,一個(gè)是 accept、一個(gè)是 read ,我們這里主要研究 read 的過(guò)程,可以分為兩個(gè)階段:等待讀就緒(等待數(shù)據(jù)到達(dá)網(wǎng)卡 & 將網(wǎng)卡的數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū))、讀數(shù)據(jù)。

阻塞IO流程如下:

圖片圖片

2.非阻塞式 IO:

非阻塞式 IO 我們應(yīng)該讓操作系統(tǒng)提供一個(gè)非阻塞的 read() 函數(shù),當(dāng)?shù)谝浑A段讀未就緒時(shí)返回 -1 ,當(dāng)讀已就緒時(shí)才進(jìn)行數(shù)據(jù)的讀取。

非阻塞IO往往需要程序員循環(huán)的方式反復(fù)嘗試讀寫(xiě)文件描述符, 這個(gè)過(guò)程稱為輪詢(for(connfd : arr)). 這對(duì)CPU來(lái)說(shuō)是較大的浪費(fèi), 一 般只有特定場(chǎng)景下才使用.

偽代碼具體如下:

arr = new Arr[];
listenfd = socket();   // 打開(kāi)一個(gè)網(wǎng)絡(luò)通信套接字
bind(listenfd);        // 綁定
listen(listenfd);      // 監(jiān)聽(tīng)
while(true) {
  connfd = accept(listenfd);  // 阻塞 等待建立連接
  arr.add(connfd);
}


// 異步線程檢測(cè) 連接是否可讀
new Tread(){
  for(connfd : arr){
    buf = new buf[1024]; // 讀取數(shù)據(jù)容器
    // 非阻塞 read 最重要的是提供了我們?cè)谝粋€(gè)線程內(nèi)管理多個(gè)文件描述符的能力
    int n = read(connfd, buf);  // 檢測(cè) connfd 是否可讀
    if(n != -1){
       newThreadDeal(buf);   // 創(chuàng)建新線程處理
       close(connfd);        // 關(guān)閉連接 
       arr.remove(connfd);   // 移除已處理的連接
    }
  }
}


newTheadDeal(buf){
  doSomeThing(buf);  // 處理數(shù)據(jù)
}

所謂非阻塞 IO 只是將第一階段的等待讀就緒改為非阻塞,但是第二階段的數(shù)據(jù)讀取還是阻塞的,非阻塞 read 最重要的是提供了我們?cè)谝粋€(gè)線程內(nèi)管理多個(gè)文件描述符的能力

非阻塞具體流程如下:

圖片圖片

3. IO多路復(fù)用(select、poll、epoll):

上面的實(shí)現(xiàn)看著很不錯(cuò),但是卻存在一個(gè)很大的問(wèn)題,我們需要不斷的調(diào)用 read() 進(jìn)行系統(tǒng)調(diào)用,這里的系統(tǒng)調(diào)用我們可以理解為分布式系統(tǒng)的 RPC 調(diào)用,性能損耗十分嚴(yán)重,因?yàn)檫@依然是用戶層的一些小把戲。

多路復(fù)用就是系統(tǒng)提供了一種函數(shù)可以同時(shí)監(jiān)控多個(gè)文件描述符的操作,這個(gè)函數(shù)就是我們常說(shuō)到的select、poll、epoll函數(shù),可以通過(guò)它們同時(shí)監(jiān)控多個(gè)文件描述符,只要有任何一個(gè)數(shù)據(jù)狀態(tài)準(zhǔn)備就緒了,就返回可讀狀態(tài),這時(shí)詢問(wèn)線程再去通知處理數(shù)據(jù)的線程,對(duì)應(yīng)線程此時(shí)再發(fā)起read()請(qǐng)求去讀取數(shù)據(jù)。實(shí)際上最核心之處在于IO多路轉(zhuǎn)接能夠同時(shí)等待多個(gè)文件描述符的就緒狀態(tài),來(lái)達(dá)到不必為每個(gè)文件描述符創(chuàng)建一個(gè)對(duì)應(yīng)的監(jiān)控線程,從而減少線程資源創(chuàng)建的目的。

select:

select 是操作系統(tǒng)提供的系統(tǒng)函數(shù),通過(guò)它我們可以將文件描述符發(fā)送給系統(tǒng),讓系統(tǒng)內(nèi)核幫我們遍歷檢測(cè)是否可讀,并告訴我們進(jìn)行讀取數(shù)據(jù)。

偽代碼如下:

arr = new Arr[];
listenfd = socket();   // 打開(kāi)一個(gè)網(wǎng)絡(luò)通信套接字
bind(listenfd);        // 綁定
listen(listenfd);      // 監(jiān)聽(tīng)
while(true) {
  connfd = accept(listenfd);  // 阻塞 等待建立連接
  arr.add(connfd);
}


// 異步線程檢測(cè) 通過(guò) select 判斷是否有連接可讀
new Tread(){
  while(select(arr) > 0){
    for(connfd : arr){
      if(connfd can read){
        // 如果套接字可讀 創(chuàng)建新線程處理
        newTheadDeal(connfd);
        arr.remove(connfd);   // 移除已處理的連接
      }
    }
  }
}


newTheadDeal(connfd){
    buf = new buf[1024]; // 讀取數(shù)據(jù)容器
    int n = read(connfd, buf);  // 阻塞讀取數(shù)據(jù)
    doSomeThing(buf);  // 處理數(shù)據(jù)
    close(connfd);        // 關(guān)閉連接 
}

流程簡(jiǎn)圖:

圖片圖片

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

1.減少大量系統(tǒng)調(diào)用。

2.系統(tǒng)內(nèi)核幫我們遍歷檢測(cè)是否可讀。

存在一些問(wèn)題:

? 每次調(diào)用需要在用戶態(tài)和內(nèi)核態(tài)之間拷貝文件描述符數(shù)組,但高并發(fā)場(chǎng)景下這個(gè)拷貝的消耗是很大的。

? 內(nèi)核檢測(cè)文件描述符可讀還是通過(guò)遍歷實(shí)現(xiàn),當(dāng)文件描述符數(shù)組很長(zhǎng)時(shí),遍歷操作耗時(shí)也很長(zhǎng)。

? 內(nèi)核檢測(cè)完文件描述符數(shù)組后,當(dāng)存在可讀的文件描述符數(shù)組時(shí),用戶態(tài)需要再遍歷檢測(cè)一遍。

poll:

? poll 和 select 原理基本一致,最大的區(qū)別是去掉了最大 1024 個(gè)文件描述符的限制。

? select 使用固定長(zhǎng)度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的個(gè)數(shù)是有限制的,在 Linux 系統(tǒng)中,由內(nèi)核中的 FD_SETSIZE 限制, 默認(rèn)最大值為 1024,只能監(jiān)聽(tīng) 0~1023 的文件描述符。

? poll 不再用 BitsMap 來(lái)存儲(chǔ)所關(guān)注的文件描述符,取而代之用動(dòng)態(tài)數(shù)組,以鏈表形式來(lái)組織,突破了 select 的文件描述符個(gè)數(shù)限制,當(dāng)然還會(huì)受到系統(tǒng)文件描述符限制。

epoll:

epoll 主要優(yōu)化了上面三個(gè)問(wèn)題實(shí)現(xiàn):

1.每次調(diào)用需要在用戶態(tài)和內(nèi)核態(tài)之間拷貝文件描述符數(shù)組,但高并發(fā)場(chǎng)景下這個(gè)拷貝的消耗是很大的。
方案:內(nèi)核中保存一份文件描述符,無(wú)需用戶每次傳入,而是僅同步修改部分。
2.內(nèi)核檢測(cè)文件描述符可讀還是通過(guò)遍歷實(shí)現(xiàn),當(dāng)文件描述符數(shù)組很長(zhǎng)時(shí),遍歷操作耗時(shí)也很長(zhǎng)。
方案:通過(guò)事件喚醒機(jī)制喚醒替代遍歷。
3.內(nèi)核檢測(cè)完文件描述符數(shù)組后,當(dāng)存在可讀的文件描述符數(shù)組時(shí),用戶態(tài)需要再遍歷檢測(cè)一遍。
方案:僅將可讀部分文件描述符同步給用戶態(tài),不需要用戶態(tài)再次遍歷。

epoll 基于高效的紅黑樹(shù)結(jié)構(gòu),提供了三個(gè)核心操作:epoll_create、epoll_ctl、epoll_wait。

epoll_create:

   用于創(chuàng)建epoll文件描述符,該文件描述符用于后續(xù)的epoll操作,參數(shù)size目前還沒(méi)有實(shí)際用處,我們只要填一個(gè)大于0的數(shù)就行。


圖片圖片

epoll_ctl:

epoll_ctl函數(shù)用于增加,刪除,修改epoll事件,epoll事件會(huì)存儲(chǔ)于內(nèi)核epoll結(jié)構(gòu)體紅黑樹(shù)中.


圖片圖片

epoll_wait函數(shù):

epoll_wait用于監(jiān)聽(tīng)套接字事件,可以通過(guò)設(shè)置超時(shí)時(shí)間timeout來(lái)控制監(jiān)聽(tīng)的行為為阻塞模式還是超時(shí)模式。


圖片圖片

整體運(yùn)轉(zhuǎn)如下:

圖片圖片

偽代碼如下:

listenfd = socket();   // 打開(kāi)一個(gè)網(wǎng)絡(luò)通信套接字
bind(listenfd);        // 綁定
listen(listenfd);      // 監(jiān)聽(tīng)
int epfd = epoll_create(...); // 創(chuàng)建 epoll 對(duì)象
while(1) {
  connfd = accept(listenfd);  // 阻塞 等待建立連接
  epoll_ctl(connfd, ...);  // 將新連接加入到 epoll 對(duì)象
}


// 異步線程檢測(cè) 通過(guò) epoll_wait 阻塞獲取可讀的套接字
new Tread(){
  while(arr = epoll_wait()){
    for(connfd : arr){
        // 僅返回可讀套接字
        newTheadDeal(connfd);
    }
  }
}


newTheadDeal(connfd){
    buf = new buf[1024]; // 讀取數(shù)據(jù)容器
    int n = read(connfd, buf);  // 阻塞讀取數(shù)據(jù)
    doSomeThing(buf);  // 處理數(shù)據(jù)
    close(connfd);        // 關(guān)閉連接 
}
LT模式和ET模式:
LT模式:水平觸發(fā):

1.socket讀觸發(fā):socket接收緩沖區(qū)有數(shù)據(jù),會(huì)一直觸發(fā)epoll_wait EPOLLIN事件,直到數(shù)據(jù)被用戶讀取完。

2.socket寫(xiě)觸發(fā):socket可寫(xiě),會(huì)一直觸發(fā)epoll_wait EPOLLOUT事件。

ET模式:邊緣觸發(fā):

1.socket讀觸發(fā):當(dāng)被監(jiān)控的 Socket 描述符上有可讀事件發(fā)生時(shí),服務(wù)器端只會(huì)從 epoll_wait 中蘇醒一次,即使進(jìn)程沒(méi)有調(diào)用 read 函數(shù)從內(nèi)核讀取數(shù)據(jù),也依然只蘇醒一次,因此我們程序要保證一次性將內(nèi)核緩沖區(qū)的數(shù)據(jù)讀取完。

2.socket寫(xiě)觸發(fā):socket可寫(xiě),會(huì)觸發(fā)一次epoll_wait EPOLLOUT事件。

epoll為什么高效:

1.紅黑樹(shù)紅黑樹(shù)提高epoll事件增刪查改效率。

2.回調(diào)通知機(jī)制:當(dāng)epoll監(jiān)聽(tīng)套接字有數(shù)據(jù)讀或者寫(xiě)時(shí),會(huì)通過(guò)注冊(cè)到socket的回調(diào)函數(shù)通知epoll,epoll檢測(cè)到事件后,將事件存儲(chǔ)在就緒隊(duì)列(rdllist)。

3.就緒隊(duì)列:epoll_wait返回成功后,會(huì)將所有就緒事件存儲(chǔ)在事件數(shù)組,用戶不需要進(jìn)行無(wú)效的輪詢,從而提高了效率。

信號(hào)驅(qū)動(dòng)IO:

   多路轉(zhuǎn)接解決了一個(gè)線程可以監(jiān)控多個(gè)fd的問(wèn)題,但是select采用無(wú)腦的輪詢就顯得有點(diǎn)暴力,因?yàn)榇蟛糠智闆r下的輪詢都是無(wú)效的,所以有人就想,別讓我總?cè)?wèn)數(shù)據(jù)是否準(zhǔn)備就緒,而是等你準(zhǔn)備就緒后主動(dòng)通知我,這邊是信號(hào)驅(qū)動(dòng)IO。

   信號(hào)驅(qū)動(dòng)IO是在調(diào)用sigaction時(shí)候建立一個(gè)SIGIO的信號(hào)聯(lián)系,當(dāng)內(nèi)核準(zhǔn)備好數(shù)據(jù)之后再通過(guò)SIGIO信號(hào)通知線程,此fd準(zhǔn)備就緒,當(dāng)線程收到可讀信號(hào)后,此時(shí)再向內(nèi)核發(fā)起recvfrom讀取數(shù)據(jù)的請(qǐng)求,因?yàn)樾盘?hào)驅(qū)動(dòng)IO的模型下,應(yīng)用線程在發(fā)出信號(hào)監(jiān)控后即可返回,不會(huì)阻塞,所以一個(gè)應(yīng)用線程也可以同時(shí)監(jiān)控多個(gè)fd。

異步 IO:

應(yīng)用只需要向內(nèi)核發(fā)送一個(gè)讀取請(qǐng)求,告訴內(nèi)核它要讀取數(shù)據(jù)后即刻返回;內(nèi)核收到請(qǐng)求后會(huì)建立一個(gè)信號(hào)聯(lián)系,當(dāng)數(shù)據(jù)準(zhǔn)備就緒,內(nèi)核會(huì)主動(dòng)把數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間,等所有操作都完成之后,內(nèi)核會(huì)發(fā)起一個(gè)通知告訴應(yīng)用,我們稱這種模式為異步IO模型。

異步IO的優(yōu)化思路是解決應(yīng)用程序需要先后發(fā)送詢問(wèn)請(qǐng)求、接收數(shù)據(jù)請(qǐng)求兩個(gè)階段的模式,在異步IO的模式下,只需要向內(nèi)核發(fā)送一次請(qǐng)求就可以完成狀態(tài)詢問(wèn)和數(shù)拷貝的所有操作。

同步和異步區(qū)別:

同步和異步關(guān)注的是消息通信機(jī)制.

同步:就是在發(fā)出一個(gè)調(diào)用時(shí),自己需要參與等待結(jié)果的過(guò)程,則為同步,前面四個(gè)IO都自己參與了,所以也稱為同步IO。

異步:則指出發(fā)出調(diào)用以后,到數(shù)據(jù)準(zhǔn)備完成,自己都未參與,則為異步IO。

責(zé)任編輯:武曉燕 來(lái)源: 京東云開(kāi)發(fā)者
相關(guān)推薦

2021-05-31 06:50:47

SelectPoll系統(tǒng)

2022-02-22 08:55:29

SelectPoll/ Epoll

2022-06-03 10:52:55

selectpolepoll

2020-11-04 07:49:04

Select

2017-03-08 15:39:11

Linuxselect函數(shù)

2023-03-01 14:32:31

redisIOEpoll

2025-06-06 00:33:00

2023-12-06 07:28:47

阻塞IO異步IO

2025-04-24 10:05:51

2022-05-09 08:37:43

IO模型Java

2023-10-26 01:33:17

2024-12-30 00:00:05

2015-10-21 10:24:05

TCPIP網(wǎng)絡(luò)協(xié)議

2021-07-14 09:48:15

Linux源碼Epoll

2019-08-14 09:43:34

項(xiàng)目管理工具

2025-05-28 02:55:00

PostgresOTelIceberg

2020-06-18 12:23:05

WiFi速度5G

2016-01-07 11:25:12

數(shù)據(jù)模型訓(xùn)練數(shù)據(jù)

2023-11-08 09:22:14

I/ORedis阻塞

2015-05-29 09:45:42

Google IOA
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)