阻塞(blocking)、非阻塞(non-blocking):最常聽到阻塞與非阻塞這兩個詞就是在函式呼叫中,比如waitid這個函式,通過NOHANG引數可以把waitid設定為非阻塞的,也就是問詢一遍子程式,看看有沒有可回收子程式,如果沒有,直接返回不等待,而如果不設定NOHANG,waitid會一直問詢子程式,直到有一個可回收子程式再返回,也就是說如果沒有子程式可回收,waitid會一直阻塞在這條函式呼叫上,後面的操作就無法執行。如果把函式呼叫籠統的規劃到一個事件的執行,那麼阻塞與非阻塞的區別就是 是否僅僅只等待事件完成(事件返回)才進行下一步操作。比如 燒開水這個例子(參考資料中有提到),如果人從水壺開始燒水到水燒開這段時間一直在水壺前等待燒水,這就是阻塞,如果人比較聰明,在水壺燒水的過程中,人去看電視了或者幹其他事情了,這就是不阻塞的,但是在這種情況下,人想要知道水是否燒開了,只能隔一段時間去看一下水壺“燒開了沒有?”,這個隔一段時間去看一下的方法 就是 輪詢。所以,再回到waitid這個例子,我們設定NOHANG引數的時候,一般都要在waitid上加一個用於輪詢的迴圈。
同步(synchronous)、非同步(asynchronous):同步和非同步,一開始看很容易把同步和阻塞的概念混淆在一起,我傾向於將同步理解為一種現象,而阻塞/非阻塞作為這個現象中的某種狀態存在。同步和非同步的區別,參考資料中的說法我覺得雖然把同步非同步區別說出來了,但是同步和阻塞還是很容易混淆。那我再對燒開水這個例子重寫一下以供參考,水壺分為 普通水壺 和 智慧水壺,普通水壺需要人手工操作,需要人判斷水是否燒開,水燒開了需要人把水壺從火上拿下來(不然會溢位來),而智慧水壺提供兩個作用,水燒開後自動關火併且發出響聲提示人水已經燒好。燒開水就分為了兩個過程,等待水開和拿開水壺,首先說同步和非同步,同步就是使用普通水壺燒水,水燒開後需要人主動拿開水壺,而等待水燒開的這個過程可以是阻塞(站那等)的和非阻塞(看電視輪詢)的,整個操作需要人的全程參與;非同步就是使用智慧水壺燒水,等待水開的過程人可以幹任何事(即非同步本身就是非阻塞的,所以非同步並不區分阻塞/非阻塞)並且人不用關心水是否燒開(敲重點),水燒開後,智慧水壺會發出響聲提示人(發出訊號),這完成了等待水開這第一過程,緊接著,智慧水壺會自動關火,這代替了人拿開水壺的這個動作完成了第二過程。可以發現,同步整個過程需要人全程參與,而非同步整個過程是兩個“人”在做,一個“人”就是這個智慧水壺,一個人就是始終在做其他事的人,這個人將燒開水這件事託管給了智慧水壺由智慧水壺完成一切過程,最後提示人完成了。因此,判斷同步只需要看,事件的監管和完成方是不是事件發起者即可。
在接下來即將要說到的IO操作中,非同步IO就是程式將IO操作完全交由核心控制,由核心中的DMA控制器完成全部IO操作並在完成後通過訊號提示程式IO操作已完成,這也是兩個“人”的事情,程式將IO託管給DMA控制器,而程式又可以幹其他的事,二者在不同的事件上各自執行操作,是並行的。
IO模式:
比如對於一個read操作發生時,它會經歷下面兩個階段:
A, 等待資料準備,資料是否拷貝到核心緩衝區;
B, 將資料從核心拷貝到使用者程式空間
如果把這兩個階段對應到前文中我寫的燒開水的兩個過程中,對於下面的幾種IO模式就容易理解的多。
《Unix網路程式設計卷1:套接字聯網API》(即UNP)中第六章對unix 系統將IO模型分為五類:阻塞IO,非阻塞IO,IO複用,訊號驅動,非同步IO。
1、阻塞IO:A階段阻塞等待,程式呼叫recvfrom詢問核心資料是否準備好,直到資料準備好再返回。(注意:由核心監聽資料,程式詢問核心資料是否準備好)
2、非阻塞IO:A階段非阻塞等待,即採用輪詢機制(即程式不斷的詢問核心資料是否準備好(不斷呼叫recvfrom),這麼做往往消耗大量CPU時間)判斷資料是否拷貝到核心緩衝區。
3、IO複用:IO複用技術是對阻塞IO的改進,首先非阻塞大量消耗CPU,不予考慮。在資料準備階段,核心監聽一個IO也好,監聽一群IO也好,都在監聽,因此複用,就是核心同時監聽程式的多個IO操作,在程式詢問時返回有資料到來的IO。此時程式不是阻塞在recvfrom上,而是阻塞在IO複用系統呼叫——select、poll、epoll上,這幾個系統呼叫會在所監聽的所有IO中某個或某幾個有資料到來時返回。
多路IO共用一個同步阻塞介面,任意IO可操作都可啟用IO操作,這是對阻塞IO的改進(主要是select和poll、epoll,關鍵是能實現同時對多個IO埠進行監聽)。此時阻塞發生在select/poll的系統呼叫上,而不是阻塞在實際的I/O系統呼叫上。IO多路複用的高階之處在於:它能同時等待多個檔案描述符,而這些檔案描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select等函式就可以返回。
4、訊號驅動:
註冊一個IO訊號事件,在資料可操作時通過SIGIO訊號通知執行緒,這應該算是一種非同步機制;
5、非同步IO:程式通知核心完成一個IO操作,由核心完成A+B階段後通知程式。
參考中的這張圖很好的解釋了上述5種IO模型。
POSIX將IO只分成了同步IO、非同步IO兩種模型。
同步I/O操作:實際的I/O操作將導致請求程式阻塞,直到I/O操作完成。
非同步I/O操作:實際的I/O操作不導致請求程式阻塞。
由此,阻塞式I/O,非阻塞式I/O,I/O複用,訊號驅動I/O模型都屬於同步I/O,因為第二階段的資料複製都是阻塞的,也可以理解為都由程式完成。而只有非同步I/O模型屬於這裡的非同步I/O操作(IO由核心完成)。
參考資料:知乎Linux分享官
《Unix網路程式設計卷1:套接字聯網API(第3版)》6.2 IO模型