IO多路複用

多路複用

關於什麼是I/O多路複用,在知乎上有個很好的回答,能夠參考羅志宇前輩的回答
記錄下本身的理解:忘記這個坑爹的中文翻譯。記住I/O multiplexing
              圖片描述linux

I/O multiplexing 這裏面的 multiplexing 指的實際上是在單個線程經過記錄跟蹤每個Sock(I/O流)的狀態來同時管理多個I/O流.
I/O多路複用這一技術。簡單來講,就是一個線程追蹤多條io流(讀,寫,異常),但不使用輪詢,而是由設備自己告知程序哪條流可用了,這樣一來就解放了cpu,也充分利用io資源,下文主要講解如何實現這一技術,linux下這一技術有三個實現,select,poll,epoll。數組

阻塞I/O模式下,內核對於I/O事件的處理是阻塞或者喚醒,而非阻塞模式下,咱們能夠經過循環把集合中的流從頭至尾問一遍,這樣就能夠處理多個流了,但這樣的作法顯然很差,由於若是全部流都沒有數據,那麼只會白白浪費CPU.
爲了不CPU空轉,能夠引進了一個代理(一開始有一位叫作select的代理,後來又有一位叫作poll的代理,不過二者的本質是同樣的)。這個代理比較厲害,能夠同時觀察許多流的I/O事件,在空閒的時候,會把當前線程阻塞掉,當有一個或多個流有I/O事件時,就從阻塞態中醒來,因而咱們的程序就會輪詢一遍全部的流安全

fcntl I/O多路複用

此種方法與select類似,不做介紹函數

I/O Multiplexing --> select

#include <sys/select.h>  
/**
 *select將更新這個集合,把其中不可讀的套節字去掉只保留符合條件的套節字在這個集合裏面    
 */
int select(int nfds, fd_set *readfds, fd_set *writefds,  
           fd_set *exceptfds, struct timeval *timeout);

參數readfds、writefds、exceptfds都是指向文件描述符的指針,數據類型爲fd_set。而readfds是用來檢測輸入的,writefds是用來檢測輸出的,exceptfds使用檢測是否異常的。有關fd_set一般有四個宏供咱們操做:FD_ZERO、FD_SET、FD_CLR、FD_ISSET。spa

  • FD_ZERO(fd_set *set); //fd_set所指向的集合清空
  • FD_SET(int fd,fd_set *set);//文件描述符fd添加到fd_set所指向的集合中
  • FD_CLR(int fd,fd_set *set);//文件描述符fd從fd_set所指向的集合中移除
  • int FD_ISSET(int fd,fd_set *set);//文件描述符fd是fd_set所指向的集合中的成員,則FD_ISSET返回true,不然返回false

文件描述符集合有一個最大容量限制,由常量FD_SETSIZE來決定,在Linux上,該常量值爲1024。
參數timeout爲超時時間。線程

#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>

int main(void)
{
        int bytes_read, ready;
        char buffer[128];
        fd_set readfds;
        struct timeval timeout;

        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);

        timeout.tv_sec = 10;
        timeout.tv_usec = 0;

        ready = select(STDIN_FILENO+1, &readfds, NULL, NULL, &timeout);

        if (ready) {
                bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));
                if (buffer[bytes_read - 1] == '\n')
                        buffer[bytes_read - 1] = '\0';
                printf("%s\n", buffer);
        } else {
                printf("No data to read\n");
        }

        return 0;
}

I/O Multiplexing --> poll

因爲I/O Multiplexing->select存在如下問題翻譯

  • select 會修改傳入的參數數組,這個對於一個須要調用不少次的函數,是很是不友好的。
  • select 若是任何一個sock(I/O stream)出現了數據,select 僅僅會返回,可是並不會告訴你是那個sock上有數據,因而你只能本身一個一個的找,10幾個sock可能還好,要是幾萬的sock每次都找一遍,這個無謂的開銷就很有海天盛筵的豪氣了。
  • select 只能監視1024個連接, 這個跟草榴沒啥關係哦,linux 定義在頭文件中的,參見FD_SETSIZE。
  • select 不是線程安全的,若是你把一個sock加入到select, 而後忽然另一個線程發現,尼瑪,這個sock不用,要收回。對不起,這個select 不支持的,若是你喪心病狂的居然關掉這個sock, select的標準行爲是。。呃。。不可預測的, 這個但是寫在文檔中的哦.

所以14年後(1997)出現了POLL,修復select的如下問題設計

  • poll 去掉了1024個連接的限制
  • poll 從設計上來講,再也不修改傳入數組
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);  

struct pollfd{  
    int fd;         //文件描述符  
    short events;   //等待的事件  
    short revents;  //實際發生的事件  
};
  • 第一個參數:

 每一個pollfd結構體指定了一個被監視的文件描述符。第一個參數是一個數組,即poll函數能夠監視多個文件描述符。每一個結構體的events是監視該文件描述符的事件掩碼,由用戶來設置。revents是文件描述符的操做結果事件,內核在調用返回時設置。events中請求的任何事件均可能在revents中返回
圖片描述3d

  • 第二個參數nfds:
    要監視的描述符的數目。
  • 第三個參數timeout
    指定等待的毫秒數,不管 I/O 是否準備好,poll() 都會返回.

圖片描述


I/O Multiplexing -->epoll

問題:使用select,咱們有O(n)的無差異輪詢複雜度,隨着處理的流越多,無差異輪詢時間就越長
此時epoll產生,epoll能夠理解爲event poll,不一樣於(select/poll)無差異輪詢,epoll之會把哪一個流發生了怎樣的I/O事件通知咱們。此時咱們對這些流的操做都是有意義的。代理

相關文章
相關標籤/搜索