1. 程式人生 > >同步,非同步,阻塞,非阻塞

同步,非同步,阻塞,非阻塞

BIO:同步並阻塞,可以通過執行緒池改善
NIO:同步非阻塞,多路複用輪詢,適合連線數多且連結比較短
AIO:非同步非阻塞,客戶端的IO請求都是由OS 完成了再通知伺服器啟動執行緒處理,適合長連線(JDK7開始支援)

同步和非同步是針對應用程式和核心互動而言的,而阻塞和非阻塞針對程序在訪問資料時根據IO的就緒狀態採取不同方式而已。

什麼是阻塞和非阻塞,什麼是同步和非同步,同步和非同步是針對應用程式和核心的互動而言的,同步指的是使用者程序觸發 IO 操作並等待或者輪詢的去檢視 IO 操作是否就緒,而非同步是指使用者程序觸發 IO 操作以後便開始做自己的事情,而當 IO 操作已經完成的時候會得到 IO 完成的通知。而阻塞和非阻塞是針對於程序在訪問資料的時候,根據 IO 操作的就緒狀態來採取的不同方式,說白了是一種讀取或者寫入操作函式的實現方式,阻塞方式下讀取或者寫入函式將一直等待,而非阻塞方式下,讀取或者寫入函式會立即返回一個狀態值。

一般來說 I/O 模型可以分為:同步阻塞,同步非阻塞,非同步阻塞,非同步非阻塞 IO
**同步阻塞 IO:**在此種方式下,使用者程序在發起一個 IO 操作以後,必須等待 IO 操作的完成,只有當真正完成了 IO 操作以後,使用者程序才能執行。
JAVA 傳統的 IO 模型屬於此種方式!
同步非阻塞 IO:在此種方式下,使用者程序發起一個 IO 操作以後邊可返回做其它事情,但是使用者程序需要時不時的詢問 IO 操作是否就緒,這就要求使用者程序不停的去詢問,從而引入不必要的 CPU 資源浪費。
其中目前 JAVA 的 NIO 就屬於同步非阻塞 IO。
**非同步阻塞 IO:**此種方式下是指應用發起一個 IO 操作以後,不等待核心 IO 操作的完成,等核心完成 IO 操作以後會通知應用程式,這其實就是同步和非同步最關鍵的區別,同步必須等待或者主動的去詢問 IO 是否完成,那麼為什麼說是阻塞的呢?因為此時是通過 select 系統呼叫來完成的,而 select 函式本身的實現方式是阻塞

的,而採用 select 函式有個好處就是它可以同時監聽多個檔案控制代碼,從而提高系統的併發性!
**非同步非阻塞 IO:**在此種模式下,使用者程序只需要發起一個 IO 操作然後立即返回,等 IO 操作真正的完成以後,應用程式會得到 IO 操作完成的通知,此時使用者程序只需要對資料進行處理就好了,不需要進行實際的 IO 讀寫操作,因為真正的 IO 讀取或者寫入操作已經由核心完成了。

Select/epoll的區別
多路複用就是同步非阻塞IO,他是利用單獨執行緒(核心級別)統一監測所有socket,一旦某個socket有了IO資料,就啟動相應的Application處理,在select和poll利用輪詢實現監測socket中是否有IO資料,而epoll改進了這種方式,底層用notify機制,即Reactor方式來監測,Java NIO也是採用這種機制

select缺點:
select的幾大缺點:
(1)每次呼叫select,都需要把fd集合從使用者態拷貝到核心態,這個開銷在fd很多時會很大
(2)同時每次呼叫select都需要在核心遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
(3)select支援的檔案描述符數量太小了,預設是1024

epoll既然是對select和poll的改進,就應該能避免上述的三個缺點。那epoll都是怎麼解決的呢?在此之前,我們先看一下epoll和select和poll的呼叫介面上的不同,select和poll都只提供了一個函式——select或者poll函式。而epoll提供了三個函式,epoll_create,epoll_ctl和epoll_wait,epoll_create是建立一個epoll控制代碼;epoll_ctl是註冊要監聽的事件型別;epoll_wait則是等待事件的產生。

對於第一個缺點,epoll的解決方案在epoll_ctl函式中。每次註冊新的事件到epoll控制代碼中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進核心,而不是在epoll_wait的時候重複拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

對於第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的裝置等待佇列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)併為每個fd指定一個回撥函式,當裝置就緒,喚醒等待佇列上的等待者時,就會呼叫這個回撥函式,而這個回撥函式會把就緒的fd加入一個就緒連結串列)。epoll_wait的工作實際上就是在這個就緒連結串列中檢視有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是類似的)。

對於第三個缺點,epoll沒有這個限制,它所支援的FD上限是最大可以開啟檔案的數目,這個數字一般遠大於2048,舉個例子,在1GB記憶體的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關係很大。

總結:

(1)select,poll實現需要自己不斷輪詢所有fd集合,直到裝置就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要呼叫epoll_wait不斷輪詢就緒連結串列,期間也可能多次睡眠和喚醒交替,但是它是裝置就緒時,呼叫回撥函式,把就緒fd放入就緒連結串列中,並喚醒在epoll_wait中進入睡眠的程序。雖然都要睡眠和交替,但是select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只要判斷一下就緒連結串列是否為空就行了,這節省了大量的CPU時間。這就是回撥機制帶來的效能提升。

(2)select,poll每次呼叫都要把fd集合從使用者態往核心態拷貝一次,並且要把current往裝置等待佇列中掛一次,而epoll只要一次拷貝,而且把current往等待佇列上掛也只掛一次(在epoll_wait的開始,注意這裡的等待佇列並不是裝置等待佇列,只是一個epoll內部定義的等待佇列)。這也能節省不少的開銷。