在IT圈混飯吃,不管你用什么編程語言、從事前端還是后端,阻塞、非阻塞、異步、同步這些概念,都需要清晰地掌握,否則,怎么與面試官談笑風(fēng)生(chui niu pi)?但是,掌握這些概念又不是非常容易,尤其對非科班出身的,更加困難。本文試圖給出一個清晰簡明但不失深刻的介紹,希望對大家有所幫助。
1、從I/O說起
這些概念之所以容易令人迷惑,在于很多人對I/O就沒有清晰準確的理解,后面的理解自然不可能正確。我想用一個具體的例子來說明一下I/O。
設(shè)想自己是一個進程,就叫小進吧。小進需要接收一個輸入,我們不管這個輸入是從網(wǎng)絡(luò)套接字來,還是鍵盤,鼠標來,輸入的來源可以千千萬萬。但是,都必須由內(nèi)核來幫小進完成,為啥內(nèi)核這么霸道?因為計算機上運行的可不只是咱小進一個進程,還有很多進程。這些進程兄弟也可能需要從這些輸入設(shè)備接收輸入,沒有內(nèi)核居中協(xié)調(diào),豈不是亂套。
從小進的角度看,內(nèi)核幫助它完成輸入,其實包括三個步驟:
- 1、內(nèi)核替小進接收好數(shù)據(jù),這些數(shù)據(jù)暫時存在內(nèi)核的內(nèi)存空間
- 2、內(nèi)核將數(shù)據(jù)從自己的內(nèi)存空間復(fù)制到小進的內(nèi)存空間
- 3、告訴小進,輸入數(shù)據(jù)來了,趕快讀吧
這三步看似挺簡單,其實在具體實現(xiàn)時,有很多地方需要考慮:
- 0、小進如何告訴內(nèi)核自己要接收一個輸入?
- 1、內(nèi)核接到小進的請求,替小進接收好數(shù)據(jù)這段時間, 小進咋辦?
- 2、內(nèi)核在將數(shù)據(jù)復(fù)制到小進的內(nèi)存空間這段時間,小進咋辦?
- 3、到底什么時候告訴小進數(shù)據(jù)準備好了,是在內(nèi)核接收好數(shù)據(jù)之后就告訴小進,還是在將數(shù)據(jù)復(fù)制到小進的內(nèi)存空間之后再告訴他?
- 4、內(nèi)核以什么樣的方式告訴小進,數(shù)據(jù)準備好了?
2、阻塞式I/O模型
對上面5個問題,最簡單的解決方案就是阻塞式I/O模型,它的過程是這樣的:
小進:內(nèi)核內(nèi)核,我要接收一個鍵盤輸入,快點幫我完成!
內(nèi)核:好咧!biubiu!內(nèi)核迅速將小進阻塞,可憐的小進頓時石化,就像被孫悟空點了定一樣。
就這樣,小進在石化中,時間一點點流逝。終于,內(nèi)核收到了數(shù)據(jù)。
內(nèi)核:數(shù)據(jù)終于來了,我要開干了!duang duang duang,先把數(shù)據(jù)存在自己的內(nèi)核空間,然后又復(fù)制到小進的用戶空間。
內(nèi)核:biubiu!內(nèi)核解除了小進的阻塞,小進瞬間復(fù)活,小進的記憶還是停留在讓內(nèi)核幫他接收輸入時。
小進:哇!內(nèi)核真靠譜,數(shù)據(jù)已經(jīng)有了!干活去!
我們可以看到,小進發(fā)出接收輸入的請求給內(nèi)核開始,就處于阻塞狀態(tài),直到內(nèi)核將數(shù)據(jù)復(fù)制到小進的用戶空間,小進才解除阻塞。
以上過程可通過下圖來解釋:

3、非阻塞式I/O
小進發(fā)現(xiàn),阻塞式I/O中,自己總要被阻塞好久,好不爽啊,于是小進改用了非阻塞式I/O,其過程是這樣的:
小進:內(nèi)核內(nèi)核,我要接收一個輸入,趕緊幫我看看,數(shù)據(jù)到了沒有,先說好,不要阻塞我。
內(nèi)核:查看了一下自己的內(nèi)核空間,沒有發(fā)現(xiàn)數(shù)據(jù),于是迅速告訴小進,沒有呢!并繼續(xù)幫小進等著數(shù)據(jù)。
如此這樣,小進不斷地問內(nèi)核,終于,過了一段時間,小進再一次詢問時,內(nèi)核往自己的空間中一查,呦!數(shù)據(jù)來了,不勝其煩的內(nèi)核迅速告訴小進,數(shù)據(jù)好了!
小進:快給我!
內(nèi)核:biu!內(nèi)核干凈利落地阻塞了小進,悲催的小進還是石化了!
內(nèi)核趕緊將自己空間的輸入數(shù)據(jù)復(fù)制到小進的用戶空間,復(fù)制好后。
內(nèi)核:biu!內(nèi)核解除了小進的阻塞,小進立馬復(fù)活
小進:哇!數(shù)據(jù)來了,啥也不說,干活!
我們看到,所謂的非阻塞I/O,其實在內(nèi)核將數(shù)據(jù)從內(nèi)核空間復(fù)制到小進的用戶空間時,小進還是被阻塞的。
具體過程如下圖所示:

4、異步I/O
上面的兩種I/O解決方案中,小進都被阻塞了,只不過是阻塞時間長短不一樣,第一種方案中小進被阻塞的時間長一些,在內(nèi)核接收數(shù)據(jù)以及將數(shù)據(jù)復(fù)制到小進的用戶空間時,都被阻塞。
第二種方案中,只在內(nèi)核將數(shù)據(jù)從內(nèi)核空間復(fù)制到小進的用戶空間時,小進才被阻塞。
我們現(xiàn)在說的異步I/O,目的就是讓小進絕對不被阻塞。其過程是這樣的:
小進:內(nèi)核內(nèi)核,我要接收一個輸入,弄好了告訴我。同時將一個信號和信號處理函數(shù)告訴內(nèi)核,然后繼續(xù)干自己的活了。
內(nèi)核:得了您嘞,您先忙。
一直到內(nèi)核接收到數(shù)據(jù)并將數(shù)據(jù)從內(nèi)核空間復(fù)制到小進的用戶空間后,內(nèi)核才給小進發(fā)送信號。小進在信號處理函數(shù)中可以直接處理數(shù)據(jù)。
其過程可用下圖解釋:

5、IO多路復(fù)用
在上面的阻塞IO中,我們的小進都是直接阻塞在IO系統(tǒng)調(diào)用recvfrom上,可以說是與IO系統(tǒng)調(diào)用“零距離”。
這種做法有一定的弊端,就是當小進要同時等待多個輸入,哪個先來就處理哪個時,小進不知道哪個先來,自然也就不知道應(yīng)該被阻塞到哪一個輸入的recvfrom上好。
聰明如你,可能會說,那全部用異步IO唄。但這樣也不方便,就是小進要為每一個輸入都在系統(tǒng)中注冊一個信號和信號處理函數(shù),這可是要與內(nèi)核打交道的。如果要等的輸入非常多,豈不是非常的麻煩。
為了方便處理這種情況,小進可以使用一個輸入管家,就是select系統(tǒng)調(diào)用,該調(diào)用可以幫助小進管理所有的輸入(通常是套接字),每當一個輸入中有數(shù)據(jù)到來時,就告訴小進。這樣小進實際上是阻塞在select系統(tǒng)調(diào)用上,而不是阻塞在真正的IO系統(tǒng)調(diào)用上,如下圖所示:

6、那啥是同步呢?
一句話,凡是讓小進阻塞(不管長短)的I/O方案都是同步I/O。也就是說,阻塞、非阻塞、信號驅(qū)動式都是同步I/O。
7、無總結(jié),不進步
上面,我們從完成輸入時,進程與內(nèi)核的交互方式的角度分析了不同的I/O解決方案,在這個過程中,解釋清楚了阻塞、非阻塞、同步、異步的概念。