同步和異步,阻塞和非阻塞
同步和異步
關注的是結果消息的通信機制
同步:同步的意思就是調用方需要主動等待結果的返回
異步:異步的意思就是不需要主動等待結果的返回,而是通過其他手段比如,狀態通知,回調函數等。
阻塞和非阻塞
主要關注的是等待結果返回調用方的狀態
阻塞:是指結果返回之前,當前線程被掛起,不做任何事
非阻塞:是指結果在返回之前,線程可以做一些其他事,不會被掛起。
兩者的組合
1.同步阻塞:同步阻塞基本也是編程中最常見的模型,打個比方你去商店買衣服,你去了之后發現衣服賣完了,那你就在店里面一直等,期間不做任何事(包括看手機),等着商家進貨,直到有貨為止,這個效率很低。
2.同步非阻塞:同步非阻塞在編程中可以抽象為一個輪詢模式,你去了商店之后,發現衣服賣完了,這個時候不需要傻傻的等着,你可以去其他地方比如奶茶店,買杯水,但是你還是需要時不時的去商店問老板新衣服到了嗎。
3.異步阻塞:異步阻塞這個編程里面用的較少,有點類似你寫了個線程池,submit然后馬上future.get(),這樣線程其實還是掛起的。有點像你去商店買衣服,這個時候發現衣服沒有了,這個時候你就給老板留給電話,說衣服到了就給我打電話,然后你就守着這個電話,一直等着他響什么事也不做。這樣感覺的確有點傻,所以這個模式用得比較少。
4.異步非阻塞:異步非阻塞。好比你去商店買衣服,衣服沒了,你只需要給老板說這是我的電話,衣服到了就打。然后你就隨心所欲的去玩,也不用操心衣服什么時候到,衣服一到,電話一響就可以去買衣服了。
五種I/O模型

阻塞I/O模型:

應用程序調用一個IO函數,導致應用程序阻塞,等待數據准備好。 如果數據沒有准備好,一直等待….數據准備好了,從內核拷貝到用戶空間,IO函數返回成功指示。
當調用recv()函數時,系統首先查是否有准備好的數據。如果數據沒有准備好,那么系統就處於等待狀態。當數據准備好后,將數據從系統緩沖區復制到用戶空間,然后該函數返回。在套接應用程序中,當調用recv()函數時,未必用戶空間就已經存在數據,那么此時recv()函數就會處於等待狀態。
非阻塞IO模型

我們把一個SOCKET接口設置為非阻塞就是告訴內核,當所請求的I/O操作無法完成時,不要將進程睡眠,而是返回一個錯誤。這樣我們的I/O操作函數將不斷的測試數據是否已經准備好,如果沒有准備好,繼續測試,直到數據准備好為止。在這個不斷測試的過程中,會大量的占用CPU的時間。上述模型絕不被推薦。
IO復用模型:

簡介:主要是select和epoll兩個系統調用;對一個IO端口,兩次調用,兩次返回,比阻塞IO並沒有什么優越性;關鍵是能實現同時對多個IO端口進行監聽;
I/O復用模型會用到select、poll、epoll函數,這幾個函數也會使進程阻塞,但是和阻塞I/O所不同的的,這兩個函數可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數。
當用戶進程調用了select,那么整個進程會被block;而同時,kernel會“監視”所有select負責的socket;當任何一個socket中的數據准備好了,select就會返回。這個時候,用戶進程再調用read操作,將數據從kernel拷貝到用戶進程。
這個圖和blocking IO的圖其實並沒有太大的不同,事實上還更差一些。因為這里需要使用兩個系統調用(select和recvfrom),而blocking IO只調用了一個系統調用(recvfrom)。但是,用select的優勢在於它可以同時處理多個connection。(多說一句:所以,如果處理的連接數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優勢並不是對於單個連接能處理得更快,而是在於能處理更多的連接。)
信號驅動IO
簡介:兩次調用,兩次返回;

首先我們允許套接口進行信號驅動I/O,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據准備好時,進程會收到一個SIGIO信號,可以在信號處理函數中調用I/O操作函數處理數據。
異步IO模型

當一個異步過程調用發出后,調用者不能立刻得到結果。實際處理這個調用的部件在完成后,通過狀態、通知和回調來通知調用者的輸入輸出操作
5個I/O模型的比較

不同I/O模型的區別,其實主要在等待數據和數據復制這兩個時間段不同,圖形中已經表示得很清楚了。
select、poll、epoll的區別? :
1、支持一個進程所能打開的最大連接數
| select |
單個進程所能打開的最大連接數有FD_SETSIZE宏定義,其大小是32個整數的大小(在32位的機器上,大小就是32*32,同理64位機器上FD_SETSIZE為32*64),當然我們可以對進行修改,然后重新編譯內核,但是性能可能會受到影響。 |
| poll |
poll本質上和select沒有區別,但是它沒有最大連接數的限制,原因是它是基於鏈表來存儲的 |
| epoll |
雖然連接數有上限,但是很大,1G內存的機器上可以打開10萬左右的連接,2G內存的機器可以打開20萬左右的連接 |
2、FD劇增后帶來的IO效率問題
| select |
因為每次調用時都會對連接進行線性遍歷,所以隨着FD的增加會造成遍歷速度慢的“線性下降性能問題”。 |
| poll |
同上 |
| epoll |
因為epoll內核中實現是根據每個fd上的callback函數來實現的,只有活躍的socket才會主動調用callback,所以在活躍socket較少的情況下,使用epoll沒有前面兩者的線性下降的性能問題,但是所有socket都很活躍的情況下,可能會有性能問題。 |
3、 消息傳遞方式
| select |
內核需要將消息傳遞到用戶空間,都需要內核拷貝動作 |
| poll |
同上 |
| epoll |
epoll通過內核和用戶空間共享一塊內存來實現的。 |
總結:
綜上,在選擇select,poll,epoll時要根據具體的使用場合以及這三種方式的自身特點。
1、表面上看epoll的性能最好,但是在連接數少並且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數回調。
2、select低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善
補充知識點:
Level_triggered(水平觸發):當被監控的文件描述符上有可讀寫事件發生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數據一次性全部讀寫完(如讀寫緩沖區太小),那么下次調用 epoll_wait()時,它還會通知你在上沒讀寫完的文件描述符上繼續讀寫,當然如果你一直不去讀寫,它會一直通知你!!!如果系統中有大量你不需要讀寫的就緒文件描述符,而它們每次都會返回,這樣會大大降低處理程序檢索自己關心的就緒文件描述符的效率!!!
Edge_triggered(邊緣觸發):當被監控的文件描述符上有可讀寫事件發生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數據全部讀寫完(如讀寫緩沖區太小),那么下次調用epoll_wait()時,它不會通知你,也就是它只會通知你一次,直到該文件描述符上出現第二次可讀寫事件才會通知你!!!這種模式比水平觸發效率高,系統不會充斥大量你不關心的就緒文件描述符!!
select(),poll()模型都是水平觸發模式,信號驅動IO是邊緣觸發模式,epoll()模型即支持水平觸發,也支持邊緣觸發,默認是水平觸發。
