漫谈五种IO模型(主讲IO多路复用)
首先引用levin的回答讓我們理清楚五種IO模型
1.阻塞I/O模型
老李去火車站買票,排隊三天買到一張退票。
耗費:在車站吃喝拉撒睡 3天,其他事一件沒干。
2.非阻塞I/O模型
老李去火車站買票,隔12小時去火車站問有沒有退票,三天后買到一張票。耗費:往返車站6次,路上6小時,其他時間做了好多事。
3.I/O復用模型
1.select/poll
老李去火車站買票,委托黃牛,然后每隔6小時電話黃牛詢問,黃牛三天內買到票,然后老李去火車站交錢領票。
耗費:往返車站2次,路上2小時,黃牛手續費100元,打電話17次
2.epoll
老李去火車站買票,委托黃牛,黃牛買到后即通知老李去領,然后老李去火車站交錢領票。
耗費:往返車站2次,路上2小時,黃牛手續費100元,無需打電話
4.信號驅動I/O模型
老李去火車站買票,給售票員留下電話,有票后,售票員電話通知老李,然后老李去火車站交錢領票。
耗費:往返車站2次,路上2小時,免黃牛費100元,無需打電話
5.異步I/O模型
老李去火車站買票,給售票員留下電話,有票后,售票員電話通知老李并快遞送票上門。
耗費:往返車站1次,路上1小時,免黃牛費100元,無需打電話
1. I/O多路復用
1.1 它的形成原因
如果一個I/O流進來,我們就開啟一個進程處理這個I/O流。那么假設現在有一百萬個I/O流進來,那我們就需要開啟一百萬個進程一一對應處理這些I/O流(——這就是傳統意義下的多進程并發處理)。思考一下,一百萬個進程,你的CPU占有率會多高,這個實現方式及其的不合理。所以人們提出了I/O多路復用這個模型,一個線程,通過記錄I/O流的狀態來同時管理多個I/O,可以提高服務器的吞吐能力。
1.2 通過它的英文單詞來理解一下I/O多路復用
I/O multiplexing 也就是我們所說的I/O多路復用,但是這個翻譯真的很不生動,所以我更喜歡將它拆開,變成 I/O multi plexing
 multi意味著多,而plex意味著叢(叢:聚集,許多事物湊在一起。),那么字面上來看I/O multiplexing 就是將多個I/O湊在一起。就像下面這張圖的前半部分一樣,中間的那條線就是我們的單個線程,它通過記錄傳入的每一個I/O流的狀態來同時管理多個IO。
1.3 I/O多路復用的實現
I/O多路復用模型我們來分析一下上面這張圖
- 當進程調用select,進程就會被阻塞
- 此時內核會監視所有select負責的的socket,當socket的數據準備好后,就立即返回。
- 進程再調用read操作,數據就會從內核拷貝到進程。
其實多路復用的實現有多種方式:select、poll、epoll
1.3.1 select實現方式
先理解一下select這個函數的形參都是什么
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
- nfds:指定待測試的描述子個數
- readfds,writefds,exceptfds:指定了我們讓內核測試讀、寫和異常條件的描述字
- fd_set:為一個存放文件描述符的信息的結構體,可以通過下面的宏進行設置。
void FD_ZERO(fd_set *fdset);
//清空集合
void FD_SET(int fd, fd_set *fdset);
//將一個給定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset);
//將一個給定的文件描述符從集合中刪除
int FD_ISSET(int fd, fd_set *fdset);
// 檢查集合中指定的文件描述符是否可以讀寫
- timeout:內核等待指定的描述字中就緒的時間長度
- 返回值:失敗-1 超時0 成功>0
#define FILE "/dev/input/mouse0"
int main(void) { int fd = -1; int sele_ret = -1; fd_set Fd_set; struct timeval time = {0}; char buf[10] = {0}; //打開設備文件 fd = open(FILE, O_RDONLY); if (-1 == fd) { perror("open error"); exit(-1); } //構建多路復用IO FD_ZERO(&Fd_set); //清除全部fd FD_SET(0, &Fd_set); //添加標準輸入 FD_SET(fd, &Fd_set); //添加鼠標 time.tv_sec = 10; //設置阻塞超時時間為10秒鐘 time.tv_usec = 0; sele_ret = select(fd+1, &Fd_set, NULL, NULL, &time); if (0 > sele_ret) { perror("select error"); exit(-1); } else if (0 == sele_ret) { printf("無數據輸入,等待超時.\n"); } else { if (FD_ISSET(0, &Fd_set)) //監聽得到得到的結果若是鍵盤,則讓去讀取鍵盤的數據 { memset(buf, 0, sizeof(buf)); read(0, buf, sizeof(buf)/2); printf("讀取鍵盤的內容是: %s.\n", buf); } if (FD_ISSET(fd, &Fd_set)) //監聽得到得到的結果若是鼠標,則去讀取鼠標的數據 { memset(buf, 0, sizeof(buf)); read(fd, buf, sizeof(buf)/2); printf("讀取鼠標的內容是: %s.\n", buf); } } //關閉鼠標設備文件 close(fd); return 0; } 1.3.2 poll實現方式
先理解一下poll這個函數的形參是什么
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- pollfd:又是一個結構體
struct pollfd {
int fd; //文件描述符 short events; //請求的事件(請求哪種操作) short revents; //返回的事件 }; 后兩個參數都與select的第一和最后一個參數概念一樣,就不細講了
- 返回值:失敗-1 超時0 成功>0
#define FILE "/dev/input/mouse0"int main(void) { int fd = -1; int poll_ret = 0; struct pollfd poll_fd[2] = {0}; char buf[100] = {0}; //打開設備文件 fd = open(FILE, O_RDONLY); if (-1 == fd) { perror("open error"); exit(-1); } //構建多路復用IO poll_fd[0].fd = 0; //鍵盤 poll_fd[0].events = POLLIN; //定義請求的事件為讀數據 poll_fd[1].fd = fd; //鼠標 poll_fd[1].events = POLLIN; //定義請求的事件為讀數據 int time = 10000; //定義超時時間為10秒鐘 poll_ret = poll(poll_fd, fd+1, time); if (0 > poll_ret) { perror("poll error"); exit(-1); } else if (0 == poll_ret) { printf("阻塞超時.\n"); } else { if (poll_fd[0].revents == poll_fd[0].events) //監聽得到得到的結果若是鍵盤,則讓去讀取鍵盤的數據 { memset(buf, 0, sizeof(buf)); read(0, buf, sizeof(buf)/2); printf("讀取鍵盤的內容是: %s.\n", buf); } if (poll_fd[1].revents == poll_fd[1].events) //監聽得到得到的結果若是鼠標,則去讀取鼠標的數據 { memset(buf, 0, sizeof(buf)); read(fd, buf, sizeof(buf)/2); printf("讀取鼠標的內容是: %s.\n", buf); } } //關閉文件 close(fd); return 0; } 1.3.3 epoll實現方式(太過復雜,為了不增加篇幅不放進來了)
epoll操作過程中會用到的重要函數
int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); - int epoll_create(int size):創建一個epoll的句柄,size表示監聽數目的大小。創建完句柄它會自動占用一個fd值,使用完epoll一定要記得close,不然fd會被消耗完。
-  int epoll_ctl:這是epoll的事件注冊函數,和select不同的是select在監聽的時候會告訴內核監聽什么樣的事件,而epoll必須在epoll_ctl先注冊要監聽的事件類型。
 它的第一個參數返回epoll_creat的執行結果
 第二個參數表示動作,用下面幾個宏表示
EPOLL_CTL_ADD:注冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三參數為監聽的fd,第四個參數是告訴內核要監聽什么事
- int epoll_wait:等待事件的發生,類似于select的調用
2. select
2.1 select函數的調用過程
a. 從用戶空間將fd_set拷貝到內核空間
 b. 注冊回調函數
 c. 調用其對應的poll方法
 d. poll方法會返回一個描述讀寫是否就緒的mask掩碼,根據這個mask掩碼給fd_set賦值。
 e. 如果遍歷完所有的fd都沒有返回一個可讀寫的mask掩碼,就會讓select的進程進入休眠模式,直到發現可讀寫的資源后,重新喚醒等待隊列上休眠的進程。如果在規定時間內都沒有喚醒休眠進程,那么進程會被喚醒重新獲得CPU,再去遍歷一次fd。
 f. 將fd_set從內核空間拷貝到用戶空間
2.2 select函數優缺點
缺點:兩次拷貝耗時、輪詢所有fd耗時,支持的文件描述符太小
優點:跨平臺支持
3. poll
3.1 poll函數的調用過程(與select完全一致)
3.2 poll函數優缺點
優點:連接數(也就是文件描述符)沒有限制(鏈表存儲)
缺點:大量拷貝,水平觸發(當報告了fd沒有被處理,會重復報告,很耗性能)
4. epoll
4.1 epoll的ET與LT模式
LT:延遲處理,當檢測到描述符事件通知應用程序,應用程序不立即處理該事件。那么下次會再次通知應用程序此事件。
ET:立即處理,當檢測到描述符事件通知應用程序,應用程序會立即處理。
ET模式減少了epoll被重復觸發的次數,效率比LT高。我們在使用ET的時候,必須采用非阻塞套接口,避免某文件句柄在阻塞讀或阻塞寫的時候將其他文件描述符的任務餓死
4.2 epoll的函數調用流程
a. 當調用epoll_wait函數的時候,系統會創建一個epoll對象,每個對象有一個evenpoll類型的結構體與之對應,結構體成員結構如下。
rbn,代表將要通過epoll_ctl向epll對象中添加的事件。這些事情都是掛載在紅黑樹中。
rdlist,里面存放的是將要發生的事件
b. 文件的fd狀態發生改變,就會觸發fd上的回調函數
 c. 回調函數將相應的fd加入到rdlist,導致rdlist不空,進程被喚醒,epoll_wait繼續執行。
 d. 有一個事件轉移函數——ep_events_transfer,它會將rdlist的數據拷貝到txlist上,并將rdlist的數據清空。
 e. ep_send_events函數,它掃描txlist的每個數據,調用關聯fd對應的poll方法去取fd中較新的事件,將取得的事件和對應的fd發送到用戶空間。如果fd是LT模式的話,會被txlist的該數據重新放回rdlist,等待下一次繼續觸發調用。
4.3 epoll的優點
- 沒有最大并發連接的限制
- 只有活躍可用的fd才會調用callback函數
- 內存拷貝是利用mmap()文件映射內存的方式加速與內核空間的消息傳遞,減少復制開銷。(內核與用戶空間共享一塊內存)
只有存在大量的空閑連接和不活躍的連接的時候,使用epoll的效率才會比select/poll高
下面引用知乎一書焚城的回答再次鞏固一下IO模型
- 阻塞IO, 給女神發一條短信, 說我來找你了, 然后就默默的一直等著女神下樓, 這個期間除了等待你不會做其他事情, 屬于備胎做法.
- 非阻塞IO, 給女神發短信, 如果不回, 接著再發, 一直發到女神下樓, 這個期間你除了發短信等待不會做其他事情, 屬于專一做法.
- IO多路復用, 是找一個宿管大媽來幫你監視下樓的女生, 這個期間你可以些其他的事情. 例如可以順便看看其他妹子,玩玩王者榮耀, 上個廁所等等. IO復用又包括 select, poll, epoll 模式. 那么它們的區別是什么?
3.1 select大媽 每一個女生下樓, select大媽都不知道這個是不是你的女神, 她需要一個一個詢問, 并且select大媽能力還有限, 最多一次幫你監視1024個妹子
3.2 poll大媽不限制盯著女生的數量, 只要是經過宿舍樓門口的女生, 都會幫你去問是不是你女神
3.3 epoll大媽不限制盯著女生的數量, 并且也不需要一個一個去問. 那么如何做呢? epoll大媽會為每個進宿舍樓的女生臉上貼上一個大字條,上面寫上女生自己的名字, 只要女生下樓了, epoll大媽就知道這個是不是你女神了, 然后大媽再通知你.
上面這些同步IO有一個共同點就是, 當女神走出宿舍門口的時候, 你已經站在宿舍門口等著女神的, 此時你屬于阻塞狀態
接下來是異步IO的情況
你告訴女神我來了, 然后你就去王者榮耀了, 一直到女神下樓了, 發現找不見你了, 女神再給你打電話通知你, 說我下樓了, 你在哪呢? 這時候你才來到宿舍門口. 此時屬于逆襲做法
作者:涼拌姨媽好吃
鏈接:https://www.jianshu.com/p/6a6845464770
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。
轉載于:https://www.cnblogs.com/fengff/p/10813497.html
總結
以上是生活随笔為你收集整理的漫谈五种IO模型(主讲IO多路复用)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 油烟机自动清洗功能怎么样??
- 下一篇: P2245 星际导航
