精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

徹底理解 IO 多路復用實現機制

開發 前端
本篇重點學習理解IO多路復用的底層實現機制。

[[346138]]

 前言

  • BIO 、NIO 、AIO 總結
  • Unix網絡編程中的五種IO模型

為了加深對 I/O多路復用機制 的理解,以及了解到多路復用也有局限性,本著打破砂鍋問到底的精神,前面我們講了BIO、NIO、AIO的基本概念以及一些常見問題,同時也回顧了Unix網絡編程中的五種IO模型。本篇重點學習理解IO多路復用的底層實現機制。

概念說明
IO 多路復用有三種實現,在介紹select、poll、epoll之前,首先介紹一下Linux操作系統中基礎的概念:

  • 用戶空間和內核空間
  • 進程切換
  • 進程的阻塞
  • 文件描述符
  • 緩存 I/O

用戶空間 / 內核空間
現在操作系統都是采用虛擬存儲器,那么對32位操作系統而言,它的尋址空間(虛擬存儲空間)為4G(2的32次方)。 操作系統的核心是內核,獨立于普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。為了保證用戶進程不能直接操作內核(kernel),保證內核的安全,操作系統將虛擬空間劃分為兩部分,一部分為內核空間,一部分為用戶空間。

針對linux操作系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱為內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為用戶空間。

進程切換
為了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,并恢復以前掛起的某個進程的執行。這種行為被稱為進程切換。因此可以說,任何進程都是在操作系統內核的支持下運行的,是與內核緊密相關的,并且進程切換是非常耗費資源的。

從一個進程的運行轉到另一個進程上運行,這個過程中經過下面這些變化:

  1. 保存處理機上下文,包括程序計數器和其他寄存器。
  2. 更新PCB信息。
  3. 把進程的PCB移入相應的隊列,如就緒、在某事件阻塞等隊列。
  4. 選擇另一個進程執行,并更新其PCB。
  5. 更新內存管理的數據結構。
  6. 恢復處理機上下文。

進程阻塞
正在執行的進程,由于期待的某些事件未發生,如請求系統資源失敗、等待某種操作的完成、新數據尚未到達或無新工作做等,則由系統自動執行阻塞原語(Block),使自己由運行狀態變為阻塞狀態。可見,進程的阻塞是進程自身的一種主動行為,也因此只有處于運行態的進程(獲得了CPU資源),才可能將其轉為阻塞狀態。當進程進入阻塞狀態,是不占用CPU資源的。

文件描述符
文件描述符(File descriptor)是計算機科學中的一個術語,是一個用于表述指向文件的引用的抽象化概念。 文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫往往會圍繞著文件描述符展開。但是文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統。

緩存I/O
緩存I/O又稱為標準I/O,大多數文件系統的默認I/O操作都是緩存I/O。在Linux的緩存I/O機制中,操作系統會將I/O的數據緩存在文件系統的頁緩存中,即數據會先被拷貝到操作系統內核的緩沖區中,然后才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。

緩存 I/O 的缺點:

數據在傳輸過程中需要在應用程序地址空間和內核進行多次數據拷貝操作,這些數據拷貝操作所帶來的 CPU 以及內存開銷是非常大的。

什么是IO多路復用?

  • IO 多路復用是一種同步IO模型,實現一個線程可以監視多個文件句柄;
  • 一旦某個文件句柄就緒,就能夠通知應用程序進行相應的讀寫操作;
  • 沒有文件句柄就緒就會阻塞應用程序,交出CPU。

多路是指網絡連接,復用指的是同一個線程

為什么有IO多路復用機制?
沒有IO多路復用機制時,有BIO、NIO兩種實現方式,但它們都有一些問題

同步阻塞(BIO)
服務端采用單線程,當 accept 一個請求后,在 recv 或 send 調用阻塞時,將無法 accept 其他請求(必須等上一個請求處理 recv 或 send 完 )(無法處理并發)

  1. // 偽代碼描述 
  2. while (true) { 
  3.  // accept阻塞 
  4.     client_fd = accept(listen_fd); 
  5.     fds.append(client_fd); 
  6.     for (fd in fds) { 
  7.      // recv阻塞(會影響上面的accept) 
  8.         if (recv(fd)) { 
  9.          // logic 
  10.         } 
  11.     } 
  • 服務端采用多線程,當 accept 一個請求后,開啟線程進行 recv,可以完成并發處理,但隨著請求數增加需要增加系統線程,大量的線程占用很大的內存空間,并且線程切換會帶來很大的開銷,10000個線程真正發生讀寫實際的線程數不會超過20%,每次accept都開一個線程也是一種資源浪費。
  1. // 偽代碼描述 
  2. while(true) { 
  3.   // accept阻塞 
  4.   client_fd = accept(listen_fd) 
  5.   // 開啟線程read數據(fd增多導致線程數增多) 
  6.   new Thread func() { 
  7.     // recv阻塞(多線程不影響上面的accept) 
  8.     if (recv(fd)) { 
  9.       // logic 
  10.     } 
  11.   }   

同步非阻塞(NIO)

  • 服務器端當 accept 一個請求后,加入 fds 集合,每次輪詢一遍 fds 集合 recv (非阻塞)數據,沒有數據則立即返回錯誤,每次輪詢所有 fd (包括沒有發生讀寫實際的 fd)會很浪費 CPU。
  1. // 偽代碼描述 
  2. while(true) { 
  3.   // accept非阻塞(cpu一直忙輪詢) 
  4.   client_fd = accept(listen_fd) 
  5.   if (client_fd != null) { 
  6.     // 有人連接 
  7.     fds.append(client_fd) 
  8.   } else { 
  9.     // 無人連接 
  10.   }   
  11.   for (fd in fds) { 
  12.     // recv非阻塞 
  13.     setNonblocking(client_fd) 
  14.     // recv 為非阻塞命令 
  15.     if (len = recv(fd) && len > 0) { 
  16.       // 有讀寫數據 
  17.       // logic 
  18.     } else { 
  19.        無讀寫數據 
  20.     } 
  21.   }   

IO多路復用
服務器端采用單線程通過 select/poll/epoll 等系統調用獲取 fd 列表,遍歷有事件的 fd 進行 accept/recv/send ,使其能支持更多的并發連接請求。

  1. // 偽代碼描述 
  2. while(true) { 
  3.   // 通過內核獲取有讀寫事件發生的fd,只要有一個則返回,無則阻塞 
  4.   // 整個過程只在調用select、poll、epoll這些調用的時候才會阻塞,accept/recv是不會阻塞 
  5.   for (fd in select(fds)) { 
  6.     if (fd == listen_fd) { 
  7.         client_fd = accept(listen_fd) 
  8.         fds.append(client_fd) 
  9.     } elseif (len = recv(fd) && len != -1) {  
  10.       // logic 
  11.     } 
  12.   }   

IO多路復用的三種實現

  • select
  • poll
  • epoll

select
它僅僅知道了,有I/O事件發生了,卻并不知道是哪那幾個流(可能有一個,多個,甚至全部),我們只能無差別輪詢所有流,找出能讀出數據,或者寫入數據的流,對他們進行操作。所以select具有O(n)的無差別輪詢復雜度,同時處理的流越多,無差別輪詢時間就越長。

select調用過程

(1)使用copy_from_user從用戶空間拷貝fd_set到內核空間

(2)注冊回調函數__pollwait

(3)遍歷所有fd,調用其對應的poll方法(對于socket,這個poll方法是sock_poll,sock_poll根據情況會調用到tcp_poll,udp_poll或者datagram_poll)

(4)以tcp_poll為例,其核心實現就是__pollwait,也就是上面注冊的回調函數。

(5)__pollwait的主要工作就是把current(當前進程)掛到設備的等待隊列中,不同的設備有不同的等待隊列,對于tcp_poll來說,其等待隊列是sk->sk_sleep(注意把進程掛到等待隊列中并不代表進程已經睡眠了)。在設備收到一條消息(網絡設備)或填寫完文件數據(磁盤設備)后,會喚醒設備等待隊列上睡眠的進程,這時current便被喚醒了。

(6)poll方法返回時會返回一個描述讀寫操作是否就緒的mask掩碼,根據這個mask掩碼給fd_set賦值。

(7)如果遍歷完所有的fd,還沒有返回一個可讀寫的mask掩碼,則會調用schedule_timeout是調用select的進程(也就是current)進入睡眠。當設備驅動發生自身資源可讀寫后,會喚醒其等待隊列上睡眠的進程。如果超過一定的超時時間(schedule_timeout指定),還是沒人喚醒,則調用select的進程會重新被喚醒獲得CPU,進而重新遍歷fd,判斷有沒有就緒的fd。

(8)把fd_set從內核空間拷貝到用戶空間。

select函數接口

  1. #include <sys/select.h> 
  2. #include <sys/time.h> 
  3.  
  4. #define FD_SETSIZE 1024 
  5. #define NFDBITS (8 * sizeof(unsigned long)) 
  6. #define __FDSET_LONGS (FD_SETSIZE/NFDBITS) 
  7.  
  8. // 數據結構 (bitmap) 
  9. typedef struct { 
  10.     unsigned long fds_bits[__FDSET_LONGS]; 
  11. } fd_set; 
  12.  
  13. // API 
  14. int select
  15.     int max_fd,  
  16.     fd_set *readset,  
  17.     fd_set *writeset,  
  18.     fd_set *exceptset,  
  19.     struct timeval *timeout 
  20. )                              // 返回值就緒描述符的數目 
  21.  
  22. FD_ZERO(int fd, fd_set* fds)   // 清空集合 
  23. FD_SET(int fd, fd_set* fds)    // 將給定的描述符加入集合 
  24. FD_ISSET(int fd, fd_set* fds)  // 判斷指定描述符是否在集合中  
  25. FD_CLR(int fd, fd_set* fds)    // 將給定的描述符從文件中刪除   

select使用示例

  1. int main() { 
  2.   /* 
  3.    * 這里進行一些初始化的設置, 
  4.    * 包括socket建立,地址的設置等, 
  5.    */ 
  6.  
  7.   fd_set read_fs, write_fs; 
  8.   struct timeval timeout; 
  9.   int max = 0;  // 用于記錄最大的fd,在輪詢中時刻更新即可 
  10.  
  11.   // 初始化比特位 
  12.   FD_ZERO(&read_fs); 
  13.   FD_ZERO(&write_fs); 
  14.  
  15.   int nfds = 0; // 記錄就緒的事件,可以減少遍歷的次數 
  16.   while (1) { 
  17.     // 阻塞獲取 
  18.     // 每次需要把fd從用戶態拷貝到內核態 
  19.     nfds = select(max + 1, &read_fd, &write_fd, NULL, &timeout); 
  20.     // 每次需要遍歷所有fd,判斷有無讀寫事件發生 
  21.     for (int i = 0; i <= max && nfds; ++i) { 
  22.       if (i == listenfd) { 
  23.          --nfds; 
  24.          // 這里處理accept事件 
  25.          FD_SET(i, &read_fd);//將客戶端socket加入到集合中 
  26.       } 
  27.       if (FD_ISSET(i, &read_fd)) { 
  28.         --nfds; 
  29.         // 這里處理read事件 
  30.       } 
  31.       if (FD_ISSET(i, &write_fd)) { 
  32.          --nfds; 
  33.         // 這里處理write事件 
  34.       } 
  35.     } 
  36.   } 

select缺點
select本質上是通過設置或者檢查存放fd標志位的數據結構來進行下一步處理。這樣所帶來的缺點是:

  • 單個進程所打開的FD是有限制的,通過 FD_SETSIZE 設置,默認1024 ;
  • 每次調用 select,都需要把 fd 集合從用戶態拷貝到內核態,這個開銷在 fd 很多時會很大;

           需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時復制開銷大

  • 對 socket 掃描時是線性掃描,采用輪詢的方法,效率較低(高并發)

           當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字注冊某個回調函數,當             他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。

poll
poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然后查詢每個fd對應的設備狀態, 但是它沒有最大連接數的限制,原因是它是基于鏈表來存儲的.

poll函數接口

  1. #include <poll.h> 
  2. // 數據結構 
  3. struct pollfd { 
  4.     int fd;                         // 需要監視的文件描述符 
  5.     short events;                   // 需要內核監視的事件 
  6.     short revents;                  // 實際發生的事件 
  7. }; 
  8.  
  9. // API 
  10. int poll(struct pollfd fds[], nfds_t nfds, int timeout); 

poll使用示例

  1. // 先宏定義長度 
  2. #define MAX_POLLFD_LEN 4096   
  3.  
  4. int main() { 
  5.   /* 
  6.    * 在這里進行一些初始化的操作, 
  7.    * 比如初始化數據和socket等。 
  8.    */ 
  9.  
  10.   int nfds = 0; 
  11.   pollfd fds[MAX_POLLFD_LEN]; 
  12.   memset(fds, 0, sizeof(fds)); 
  13.   fds[0].fd = listenfd; 
  14.   fds[0].events = POLLRDNORM; 
  15.   int max  = 0;  // 隊列的實際長度,是一個隨時更新的,也可以自定義其他的 
  16.   int timeout = 0; 
  17.  
  18.   int current_size = max
  19.   while (1) { 
  20.     // 阻塞獲取 
  21.     // 每次需要把fd從用戶態拷貝到內核態 
  22.     nfds = poll(fds, max+1, timeout); 
  23.     if (fds[0].revents & POLLRDNORM) { 
  24.         // 這里處理accept事件 
  25.         connfd = accept(listenfd); 
  26.         //將新的描述符添加到讀描述符集合中 
  27.     } 
  28.     // 每次需要遍歷所有fd,判斷有無讀寫事件發生 
  29.     for (int i = 1; i < max; ++i) {      
  30.       if (fds[i].revents & POLLRDNORM) {  
  31.          sockfd = fds[i].fd 
  32.          if ((n = read(sockfd, buf, MAXLINE)) <= 0) { 
  33.             // 這里處理read事件 
  34.             if (n == 0) { 
  35.                 close(sockfd); 
  36.                 fds[i].fd = -1; 
  37.             } 
  38.          } else { 
  39.              // 這里處理write事件      
  40.          } 
  41.          if (--nfds <= 0) { 
  42.             break;        
  43.          }    
  44.       } 
  45.     } 
  46.   } 

poll缺點
它沒有最大連接數的限制,原因是它是基于鏈表來存儲的,但是同樣有缺點:

  • 每次調用 poll ,都需要把 fd 集合從用戶態拷貝到內核態,這個開銷在 fd 很多時會很大;
  • 對 socket 掃描是線性掃描,采用輪詢的方法,效率較低(高并發時)

epoll
epoll可以理解為event poll,不同于忙輪詢和無差別輪詢,epoll會把哪個流發生了怎樣的I/O事件通知我們。所以我們說epoll實際上是**事件驅動(每個事件關聯上fd)**的,此時我們對這些流的操作都是有意義的。(復雜度降低到了O(1))

epoll函數接口
當某一進程調用epoll_create方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關。eventpoll結構體如下所示:

  1. #include <sys/epoll.h> 
  2.  
  3. // 數據結構 
  4. // 每一個epoll對象都有一個獨立的eventpoll結構體 
  5. // 用于存放通過epoll_ctl方法向epoll對象中添加進來的事件 
  6. // epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可 
  7. struct eventpoll { 
  8.     /*紅黑樹的根節點,這顆樹中存儲著所有添加到epoll中的需要監控的事件*/ 
  9.     struct rb_root  rbr; 
  10.     /*雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件*/ 
  11.     struct list_head rdlist; 
  12. }; 
  13.  
  14. // API 
  15. int epoll_create(int size); // 內核中間加一個 ep 對象,把所有需要監聽的 socket 都放到 ep 對象中 
  16. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // epoll_ctl 負責把 socket 增加、刪除到內核紅黑樹 
  17. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);// epoll_wait 負責檢測可讀隊列,沒有可讀 socket 則阻塞進程 

每一個epoll對象都有一個獨立的eventpoll結構體,用于存放通過epoll_ctl方法向epoll對象中添加進來的事件。這些事件都會掛載在紅黑樹中,如此,重復添加的事件就可以通過紅黑樹而高效的識別出來(紅黑樹的插入時間效率是lgn,其中n為紅黑樹元素個數)。

而所有添加到epoll中的事件都會與設備(網卡)驅動程序建立回調關系,也就是說,當相應的事件發生時會調用這個回調方法。這個回調方法在內核中叫ep_poll_callback,它會將發生的事件添加到rdlist雙鏈表中。

在epoll中,對于每一個事件,都會建立一個epitem結構體,如下所示:

  1. struct epitem{ 
  2.     struct rb_node  rbn;//紅黑樹節點 
  3.     struct list_head    rdllink;//雙向鏈表節點 
  4.     struct epoll_filefd  ffd;  //事件句柄信息 
  5.     struct eventpoll *ep;    //指向其所屬的eventpoll對象 
  6.     struct epoll_event event; //期待發生的事件類型 

當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發生的事件復制到用戶態,同時將事件數量返回給用戶。

從上面的講解可知:通過紅黑樹和雙鏈表數據結構,并結合回調機制,造就了epoll的高效。 講解完了Epoll的機理,我們便能很容易掌握epoll的用法了。一句話描述就是:三步曲。

  • 第一步:epoll_create()系統調用。此調用返回一個句柄,之后所有的使用都依靠這個句柄來標識。
  • 第二步:epoll_ctl()系統調用。通過此調用向epoll對象中添加、刪除、修改感興趣的事件,返回0標識成功,返回-1表示失敗。
  • 第三部:epoll_wait()系統調用。通過此調用收集收集在epoll監控中已經發生的事件。

epoll使用示例

  1. int main(int argc, char* argv[]) 
  2.    /* 
  3.    * 在這里進行一些初始化的操作, 
  4.    * 比如初始化數據和socket等。 
  5.    */ 
  6.  
  7.     // 內核中創建ep對象 
  8.     epfd=epoll_create(256); 
  9.     // 需要監聽的socket放到ep中 
  10.     epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); 
  11.   
  12.     while(1) { 
  13.       // 阻塞獲取 
  14.       nfds = epoll_wait(epfd,events,20,0); 
  15.       for(i=0;i<nfds;++i) { 
  16.           if(events[i].data.fd==listenfd) { 
  17.               // 這里處理accept事件 
  18.               connfd = accept(listenfd); 
  19.               // 接收新連接寫到內核對象中 
  20.               epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); 
  21.           } else if (events[i].events&EPOLLIN) { 
  22.               // 這里處理read事件 
  23.               read(sockfd, BUF, MAXLINE); 
  24.               //讀完后準備寫 
  25.               epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); 
  26.           } else if(events[i].events&EPOLLOUT) { 
  27.               // 這里處理write事件 
  28.               write(sockfd, BUF, n); 
  29.               //寫完后準備讀 
  30.               epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); 
  31.           } 
  32.       } 
  33.     } 
  34.     return 0; 

epoll的優點

  • 沒有最大并發連接的限制,能打開的FD的上限遠大于1024(1G的內存上能監聽約10萬個端口);
  • 效率提升,不是輪詢的方式,不會隨著FD數目的增加效率下降。只有活躍可用的FD才會調用callback函數;即Epoll最大的優點就在于它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高于select和poll;
  • 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷。

epoll缺點

  • epoll只能工作在 linux 下

epoll LT 與 ET 模式的區別
epoll 有 EPOLLLT 和 EPOLLET 兩種觸發模式,LT 是默認的模式,ET 是 “高速” 模式。

  • LT 模式下,只要這個 fd 還有數據可讀,每次 epoll_wait 都會返回它的事件,提醒用戶程序去操作;
  • ET 模式下,它只會提示一次,直到下次再有數據流入之前都不會再提示了,無論 fd 中是否還有數據可讀。所以在 ET 模式下,read 一個 fd 的時候一定要把它的 buffer 讀完,或者遇到 EAGIN 錯誤。

epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內核就會采用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知。

select/poll/epoll之間的區別
select,poll,epoll都是IO多路復用的機制。I/O多路復用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。

epoll跟select都能提供多路I/O復用的解決方案。在現在的Linux內核里有都能夠支持,其中epoll是Linux所特有,而select則應該是POSIX所規定,一般操作系統均有實現

epoll是Linux目前大規模網絡并發程序開發的首選模型。在絕大多數情況下性能遠超select和poll。目前流行的高性能web服務器Nginx正式依賴于epoll提供的高效網絡套接字輪詢服務。但是,在并發連接不高的情況下,多線程+阻塞I/O方式可能性能更好。

支持一個進程所能打開的最大連接數

  • select:單個進程所能打開的最大連接數有FD_SETSIZE宏定義,其大小是32個整數的大小(在32位的機器上,大小就是32_32,同理64位機器上FD_SETSIZE為32_64),當然我們可以對進行修改,然后重新編譯內核,但是性能可能會受到影響,這需要進一步的測試。
  • poll:poll本質上和select沒有區別,但是它沒有最大連接數的限制,原因是它是基于鏈表來存儲的。
  • epoll:雖然連接數有上限,但是很大,1G內存的機器上可以打開10萬左右的連接,2G內存的機器可以打開20萬左右的連接。

FD劇增后帶來的IO效率問題

  • select:因為每次調用時都會對連接進行線性遍歷,所以隨著FD的增加會造成遍歷速度慢的“線性下降性能問題”。
  • poll:同上
  • epoll:因為epoll內核中實現是根據每個fd上的callback函數來實現的,只有活躍的socket才會主動調用callback,所以在活躍socket較少的情況下,使用epoll沒有前面兩者的線性下降的性能問題,但是所有socket都很活躍的情況下,可能會有性能問題。

消息傳遞方式

  • select:內核需要將消息傳遞到用戶空間,都需要內核拷貝動作
  • poll:同上
  • epoll:epoll通過內核和用戶空間共享一塊內存來實現的。

總結
select,poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,并喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,但是select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只要判斷一下就緒鏈表是否為空就行了,這節省了大量的CPU時間。這就是回調機制帶來的性能提升。

select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,并且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,而且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,注意這里的等待隊列并不是設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省不少的開銷。

高頻面試題
什么是IO多路復用?
看完上面的文章,相信你可以回答出來了。

nginx/redis 所使用的IO模型是什么?
Nginx的IO模型
Nginx 支持多種并發模型,并發模型的具體實現根據系統平臺而有所不同。

在支持多種并發模型的平臺上,nginx 自動選擇最高效的模型。但我們也可以使用 use 指令在配置文件中顯式地定義某個并發模型。

NGINX中支持的并發模型:

1、select
IO多路復用、標準并發模型。在編譯 nginx 時,如果所使用的系統平臺沒有更高效的并發模型,select 模塊將被自動編譯。configure 腳本的選項:–with-select_module 和 --without-select_module 可被用來強制性地開啟或禁止 select 模塊的編譯

2、poll
IO多路復用、標準并發模型。與 select 類似,在編譯 nginx 時,如果所使用的系統平臺沒有更高效的并發模型,poll 模塊將被自動編譯。configure 腳本的選項:–with-poll_module 和 --without-poll_module 可用于強制性地開啟或禁止 poll 模塊的編譯

3、epoll
IO多路復用、高效并發模型,可在 Linux 2.6+ 及以上內核可以使用

4、kqueue
IO多路復用、高效并發模型,可在 FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0, and Mac OS X 平臺中使用

5、/dev/poll
高效并發模型,可在 Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+, and Tru64 UNIX 5.1A+ 平臺使用

6、eventport
高效并發模型,可用于 Solaris 10 平臺,PS:由于一些已知的問題,建議 使用/dev/poll替代。

Redis IO多路復用技術
redis 是一個單線程卻性能非常好的內存數據庫, 主要用來作為緩存系統。 redis 采用網絡IO多路復用技術來保證在多連接的時候, 系統的高吞吐量。

為什么 Redis 中要使用 I/O 多路復用這種技術呢?

首先,Redis 是跑在單線程中的,所有的操作都是按照順序線性執行的,但是由于讀寫操作等待用戶輸入或輸出都是阻塞的,所以 I/O 操作在一般情況下往往不能直接返回,這會導致某一文件的 I/O 阻塞導致整個進程無法對其它客戶提供服務,而 I/O 多路復用 就是為了解決這個問題而出現的。

redis的io模型主要是基于epoll實現的,不過它也提供了 select和kqueue的實現,默認采用epoll。

select、poll、epoll之間的區別
看完上面的文章,相信你可以回答出來了。

epoll 水平觸發(LT)與 邊緣觸發(ET)的區別?
EPOLL事件有兩種模型:

  • Edge Triggered (ET) 邊緣觸發只有數據到來,才觸發,不管緩存區中是否還有數據。
  • Level Triggered (LT) 水平觸發只要有數據都會觸發。

看完上面的文章,相信你可以回答出來了。

 

責任編輯:姜華 來源: 今日頭條
相關推薦

2023-11-07 08:19:35

IO多路復用磁盤、

2022-08-26 00:21:44

IO模型線程

2024-08-08 14:57:32

2023-01-09 10:04:47

IO多路復用模型

2023-12-13 09:45:49

模型程序

2021-05-31 06:50:47

SelectPoll系統

2024-09-26 16:01:52

2023-05-08 00:06:45

Go語言機制

2009-06-29 18:09:12

多路復用Oracle

2022-07-11 08:02:15

KafkaSelector

2023-03-01 14:32:31

redisIOEpoll

2025-05-07 11:54:05

2020-10-13 07:51:03

五種IO模型

2011-12-08 10:51:25

JavaNIO

2022-01-06 14:45:10

數據庫連接池IO

2023-12-06 07:16:31

Go語言語句

2025-06-06 00:33:00

2025-04-24 10:05:51

2023-08-07 08:52:03

Java多路復用機制

2025-07-02 01:00:00

點贊
收藏

51CTO技術棧公眾號

欧美xxxx吸乳| 91色中文字幕| 欧美激情aaa| 国产美女久久| 一区二区三区.www| 久久一区免费| 亚洲香蕉在线视频| 国产精品porn| 亚洲区一区二区| www.午夜av| 亚洲欧美韩国| 亚洲摸摸操操av| 蜜桃av噜噜一区二区三| 国产老妇伦国产熟女老妇视频| 影音先锋日韩资源| 神马久久桃色视频| 国产又黄又粗又猛又爽的视频| 国产69精品久久| 午夜精品成人在线视频| 亚洲免费在线精品一区| 天天干,夜夜爽| 久久91精品国产91久久小草| 69精品小视频| 欧美黑人性猛交xxx| 亚洲a级精品| 日韩一区二区影院| 亚洲黄色a v| yellow在线观看网址| 国产日韩视频一区二区三区| 国产精成人品localhost| 在线免费观看一区二区| 美女日韩在线中文字幕| 欧美激情在线视频二区| 日韩成人短视频| 精品高清久久| 精品爽片免费看久久| 亚洲精品乱码久久久久久9色| 成人免费影院| 婷婷亚洲久悠悠色悠在线播放| 一区二区不卡在线视频 午夜欧美不卡' | 国产毛片一区二区三区| 精品国产乱码91久久久久久网站| 午夜剧场高清版免费观看| 在线日本欧美| 色又黄又爽网站www久久| 成人精品视频在线播放| 91福利国产在线观看菠萝蜜| 国产精品理伦片| 亚洲激情一区二区| 日本黄在线观看| 91视频免费观看| 国产女主播一区二区| 精品人妻伦一区二区三区久久| 精品无人码麻豆乱码1区2区| 国产精品一区专区欧美日韩| 中文无码精品一区二区三区| 欧美a级一区二区| 国产精品福利在线观看| 久草视频一区二区| 三级欧美在线一区| 国产999精品视频| 久久久蜜桃一区二区| 日日骚欧美日韩| 国产精品三级美女白浆呻吟| 在线观看中文字幕av| 精品一区二区三区久久久| 国产综合视频在线观看| 国产偷人妻精品一区二区在线| 国产一区二区三区四| 99re在线| 婷婷av一区二区三区| 久久综合久久鬼色| 日韩亚洲视频| 国产在线高潮| 夜夜嗨av一区二区三区网页 | 日韩成人免费电影| 国产精品美女久久久久久免费 | 国产av 一区二区三区| 欧美激情性爽国产精品17p| 色中色综合影院手机版在线观看| 欧美久久久久久久久久久久| 亚洲精品社区| 日本亚洲欧洲色| 亚洲系列在线观看| 国产成人av福利| 另类欧美小说| 麻豆传媒视频在线| 午夜精彩视频在线观看不卡| 无限资源日本好片| 欧美日韩黄色| 精品夜色国产国偷在线| 三级黄色在线观看| 亚洲三级观看| 国产美女久久精品香蕉69| 国产av一区二区三区精品| 97久久超碰精品国产| 亚洲精品国产精品国自产| 午夜av在线播放| 91精品福利在线| 在线免费黄色小视频| 日韩大片在线免费观看| 日韩中文字幕免费视频| 日韩欧美国产亚洲| 极品少妇一区二区三区精品视频 | 久久密一区二区三区| 欧美精品第一页在线播放| 一级一片免费看| 国产传媒欧美日韩成人| 欧洲一区二区在线| 欧美伦理免费在线| 欧美日韩亚洲综合在线| 亚洲一区二区在线免费| 国产精品久久久久久久久妇女| 亚州av一区二区| 国产片高清在线观看| 久久久综合九色合综国产精品| 亚洲精品视频一二三| 九色porny丨国产首页在线| 欧美二区乱c少妇| 永久免费看mv网站入口78| 午夜日韩在线| 国产在线高清精品| 欧洲一级在线观看| 亚洲成人自拍一区| 免费人成视频在线播放| 色综合咪咪久久网| 国产成人高清激情视频在线观看| 性一交一乱一乱一视频| 中文字幕一区二区三区四区不卡 | 亚洲不卡系列| 日韩精品视频免费在线观看| 久久久香蕉视频| 狠狠色丁香久久婷婷综合丁香| 日韩精品国内| 91福利区在线观看| 欧美xxxxx牲另类人与| 永久免费未视频| 日本在线不卡视频一二三区| 蜜桃久久精品乱码一区二区| 九色porny视频在线观看| 精品久久久久久久久久久久久久久| 三级全黄做爰视频| 精品在线亚洲视频| 中文字幕一区二区三区精彩视频| 成人不卡视频| 最近2019好看的中文字幕免费| 中文字幕一区在线播放| a美女胸又www黄视频久久| 欧美这里只有精品| 亚洲精品福利| 欧美精品成人在线| 欧美一级片免费| 亚洲国产乱码最新视频 | 国产日韩欧美综合在线| av无码精品一区二区三区| 羞羞色国产精品网站| 欧美亚洲成人免费| 欧美白人做受xxxx视频| 日韩欧美综合在线视频| 强伦人妻一区二区三区| 日韩精品五月天| 亚洲狠狠婷婷综合久久久| 欧美黄色成人| 欧美乱大交做爰xxxⅹ性3| 亚洲第一色视频| 亚洲高清久久久| 日本免费福利视频| 狂野欧美一区| 一本色道婷婷久久欧美 | 日韩欧美在线一区二区三区| 久久久精品国产sm调教| av在线一区二区| 黑鬼大战白妞高潮喷白浆| 日本精品三区| 99热在线国产| 男人av在线播放| 国产一区二区三区在线观看网站 | 久久夜色精品亚洲| 久久久久久麻豆| 成人综合久久网| 欧美日韩中文| 日本高清不卡一区二区三| 玖玖精品在线| 久久人人爽人人爽人人片av高请| 欧美3p视频在线观看| 欧美日韩午夜影院| 久草免费在线观看视频| 久久看人人爽人人| 亚洲精品中文字幕乱码无线| 亚洲精一区二区三区| 日韩久久久久久久久久久久久| 婷婷精品久久久久久久久久不卡| 欧美精品18videosex性欧美| 可以在线观看的av网站| 91精品国产欧美一区二区18| 天天爽夜夜爽夜夜爽精品| 国产精品欧美综合在线| 性欧美18—19sex性高清| 久久久久国产精品午夜一区| 老汉色影院首页| 久久av网址| 成人av影视在线| 国产精品4hu.www| 午夜精品久久久久久久99热浪潮| 91精品国产综合久久久久久豆腐| 日韩精品一区二区三区在线观看 | 日韩欧美亚洲国产另类| 懂色av中文字幕| 亚洲综合成人在线| 99久久99久久精品免费看小说. | 香蕉久久国产| 人妻互换免费中文字幕| 欧美一区二区麻豆红桃视频| 国产精品一区二区欧美| 日韩专区视频| 国产精品27p| 僵尸再翻生在线观看| 日韩视频在线一区| 少妇性bbb搡bbb爽爽爽欧美| 日韩午夜在线观看视频| 在线免费av网| 在线亚洲+欧美+日本专区| 久久免费黄色网址| 亚洲天堂2014| 很污很黄的网站| 国产欧美一区二区三区沐欲| 黄色录像a级片| 成人污污视频在线观看| 亚洲欧美日韩中文字幕在线观看| 蜜臀久久99精品久久久画质超高清 | 欧美偷拍一区二区| 午夜婷婷在线观看| 午夜精品成人在线| 日本三级午夜理伦三级三| 一区二区三区自拍| 国产黄色的视频| 亚洲色图清纯唯美| www.av免费| 中文字幕一区二| 女性裸体视频网站| 国产精品嫩草影院com| 成年人在线免费看片| 久久精品欧美一区二区三区不卡 | 成人欧美一区二区三区| 亚洲一级片在线播放| 国产免费观看久久| 色欲AV无码精品一区二区久久| 久久亚洲综合av| 国产精品亚洲无码| 久久久久久久国产精品影院| 自拍偷拍亚洲天堂| 国产日韩欧美亚洲| 91激情视频在线观看| 中文字幕免费一区| 久久久99999| 亚洲蜜桃精久久久久久久| 日韩在线观看视频一区二区| 亚洲精品免费视频| 久久精品国产亚洲av无码娇色| 亚洲国产成人高清精品| 日韩av在线天堂| 一本到不卡精品视频在线观看| av手机天堂网| 欧美丰满少妇xxxxx高潮对白| 国产精品久久综合青草亚洲AV| 3d动漫精品啪啪1区2区免费 | 中日韩精品视频在线观看| 精品色蜜蜜精品视频在线观看| 天天操天天操天天操天天| 欧美制服丝袜第一页| 国产精品国产三级国产aⅴ| 日韩一区二区免费视频| 色噜噜在线播放| 亚洲无限av看| 成人午夜在线影视| 97精品一区二区视频在线观看| xx欧美视频| 国产欧美日韩丝袜精品一区| 日韩一二三区在线观看| 久久99影院| 99精品在线观看| 国产曰肥老太婆无遮挡| 六月天综合网| 在线观看日本www| aaa欧美日韩| 国产在视频线精品视频| 亚洲午夜精品在线| 国产情侣小视频| 欧美草草影院在线视频| 国产在线中文字幕| 欧美疯狂xxxx大交乱88av| 免费观看欧美大片| 91久久国产婷婷一区二区| 欧美激情极品| 国产精品久久成人免费观看| 国产日韩亚洲| 亚洲第一成肉网| 久久精品一区二区| 欧美日韩在线视频免费| 色综合久久综合| 国产黄色一区二区| 在线观看亚洲视频| av成人 com a| 91久久在线播放| 少妇精品久久久一区二区三区 | 五月婷六月丁香| 亚洲成人动漫在线观看| 91国产精品一区| 亚洲人成亚洲人成在线观看| 成人影音在线| 91精品久久久久久久久久久久久久| 欧美电影在线观看完整版| 好吊色视频988gao在线观看| 视频在线在亚洲| 国产制服丝袜在线| 亚洲国产视频直播| 国产手机视频在线| 在线观看日韩欧美| 久久久一本精品| 久久综合中文色婷婷| 午夜亚洲福利| 一级黄色高清视频| 国产精品美女久久久久久| 男人天堂av在线播放| 亚洲国产精品网站| 久久一卡二卡| 亚洲精品日产aⅴ| 国产精品久久久久久久免费观看| 无码人妻h动漫| 26uuu精品一区二区| 精品一区二区三区人妻| 日韩色视频在线观看| 黄色视屏免费在线观看| 国产精品一区二区三| 欧美日韩国产高清电影| 男女av免费观看| 91啪亚洲精品| 青青草免费观看视频| 亚洲精品久久久久久久久久久久久 | 国产精品天干天干在观线| 天堂网一区二区| 亚洲香蕉成人av网站在线观看| 美女福利一区二区| 欧美一级爽aaaaa大片| 久久xxxx| www.av天天| 欧美中文字幕不卡| 午夜伦全在线观看| 成人高h视频在线| 亚洲精彩视频| 国偷自产av一区二区三区麻豆| 亚洲欧美日韩成人高清在线一区| 国产日韩精品suv| 欧美成年人视频网站| 日韩欧美中文字幕在线视频 | 精品三级在线看| yellow字幕网在线| 欧美精品中文字幕一区二区| 老司机精品视频网站| 国产精品久久久视频| 欧美精品在线观看一区二区| 顶级网黄在线播放| 99九九视频| 在线亚洲观看| 欧美 日韩 国产 成人 在线观看| 欧美视频在线观看一区| 麻豆影院在线观看| 高清av免费一区中文字幕| 国产一区亚洲| 香蕉视频黄色在线观看| 欧美主播一区二区三区美女| 米奇777四色精品人人爽| av一区二区三区在线观看| 99国产精品99久久久久久粉嫩| 泷泽萝拉在线播放| 欧美人与禽zozo性伦| 制服丝袜在线播放| 久久综合九色99| 美女脱光内衣内裤视频久久影院| 黑鬼狂亚洲人videos| 亚洲国产小视频| 男女啪啪999亚洲精品| 国产一区二区片| 国产亚洲成年网址在线观看| 国产精品无码天天爽视频| 97在线观看视频国产| 三上亚洲一区二区| 在线观看成人动漫| 欧美无乱码久久久免费午夜一区| 成人看av片| 欧美日韩最好看的视频| 国产一区二区三区四区五区入口| 成人毛片在线播放| 久久亚洲精品成人| 亚洲精品中文字幕99999| 四川一级毛毛片| 91黄视频在线观看| av资源在线看片| 综合视频在线观看| 久久久久免费观看|