這次,讓我們捋清:同步、異步、阻塞、非阻塞
大家好,我是yes。
繼上一篇說(shuō)透I/O模型后,我們來(lái)談?wù)劸W(wǎng)絡(luò) I/O 經(jīng)常會(huì)伴隨的幾個(gè)容易令人混淆的概念:同步、異步、阻塞、非阻塞的區(qū)別。
這篇寫(xiě)完之后鋪墊就差不多了,之后就正式開(kāi)始 Netty 深度剖析了,敬請(qǐng)期待,嘻嘻。
話不多說(shuō),發(fā)車!
同步&異步
同步和異步指的是:當(dāng)前線程是否需要等待方法調(diào)用執(zhí)行完畢。
比如你調(diào)用一個(gè)搬運(yùn)一百塊石頭的方法:
同步指的是調(diào)用這個(gè)方法,你的線程需要等待這一百塊石頭搬完,然后得到搬完了的結(jié)果,接著再繼續(xù)執(zhí)行剩下的代碼邏輯。
//同步方式
result?=?搬一百塊石頭();
//需等待搬完的結(jié)果,才能執(zhí)行下面的邏輯
if(result)?{
石頭搬完了發(fā)工資();
}
計(jì)算下一次搬石頭的任務(wù)();
異步指的是調(diào)用這個(gè)方法,立馬就直接返回,不必等候這一百塊石頭還未搬完,可以立馬執(zhí)行后面的代碼邏輯,然后利用回調(diào)或者事件通知的方式得到石頭已經(jīng)搬完的結(jié)果。
//異步方式
搬一百塊石頭({
????//回調(diào)
?石頭搬完了發(fā)工資();
});
//不必等待石頭搬完,立馬執(zhí)行下面的邏輯
計(jì)算下一次搬石頭的任務(wù)();
可以很直觀的看出,同步和異步就是調(diào)用方式的不同,這使得我們的編碼方式也有所不同。
在異步調(diào)用下的代碼邏輯相對(duì)而言不太直觀,需要借助回調(diào)或事件通知,這在復(fù)雜邏輯下對(duì)編碼能力的要求較高。而同步調(diào)用就是直來(lái)直去,等待執(zhí)行完畢然后拿到結(jié)果緊接著執(zhí)行下面的邏輯,對(duì)編碼能力的要求較低,也更不容易出錯(cuò)。
所以你會(huì)發(fā)現(xiàn)有很多方法它是異步調(diào)用的方式,但是最終的使用還是異步轉(zhuǎn)同步。
比如你向線程池提交一個(gè)任務(wù),得到一個(gè) future,此時(shí)是異步的,然后你在緊接著在代碼里調(diào)用 future.get(),那就變成等待這個(gè)任務(wù)執(zhí)行完成,這就是所謂的異步轉(zhuǎn)同步,像 Dubbo RPC 調(diào)用同步得到返回結(jié)果就是這樣實(shí)現(xiàn)的。
阻塞&非阻塞
阻塞和非阻塞指的是:當(dāng)前接口數(shù)據(jù)還未準(zhǔn)備就緒時(shí),線程是否被阻塞掛起。
何為阻塞掛起?就是當(dāng)前線程還處于 CPU 時(shí)間片當(dāng)中,調(diào)用了阻塞的方法,由于數(shù)據(jù)未準(zhǔn)備就緒,則時(shí)間片還未到就讓出 CPU。
所以阻塞和同步看起來(lái)都是等,但是本質(zhì)上它們不一樣,同步的時(shí)候可沒(méi)有讓出 CPU。
而非阻塞就是當(dāng)前接口數(shù)據(jù)還未準(zhǔn)備就緒時(shí),線程不會(huì)被阻塞掛起,可以不斷輪詢請(qǐng)求接口,看看數(shù)據(jù)是否已經(jīng)準(zhǔn)備就緒。
至此我們可以得到一個(gè)結(jié)論:
同步&異步指:當(dāng)數(shù)據(jù)還未處理完成時(shí),代碼的邏輯處理方式不同。 阻塞&非阻塞指:當(dāng)數(shù)據(jù)還未處理完成時(shí)(未就緒),線程的狀態(tài)。
所以同步&異步其實(shí)是處于框架這種高層次維度來(lái)看待的,而阻塞&非阻塞往往針對(duì)底層的系統(tǒng)調(diào)用方面來(lái)抉擇,也就是說(shuō)兩者是從不同維度來(lái)考慮的。
再結(jié)合 I/O 來(lái)看
前提:程序和硬件之間隔了個(gè)操作系統(tǒng),而為了安全考慮,Linux 系統(tǒng)分了:用戶態(tài)和內(nèi)核態(tài)
在這個(gè)前提下,我們?cè)倜鞔_ I/O 操作有兩個(gè)步驟:
發(fā)起 I/O 請(qǐng)求 實(shí)際 I/O 讀寫(xiě),即數(shù)據(jù)從內(nèi)核緩存拷貝到用戶空間
阻塞 I/O 和非阻塞 I/O。按照上文,其實(shí)指的就是用戶線程是否被阻塞,這里指代的步驟1(發(fā)起I/O請(qǐng)求)。
阻塞 I/O,指用戶線程發(fā)起 I/O 請(qǐng)求的時(shí)候,如果數(shù)據(jù)還未準(zhǔn)備就緒(例如暫無(wú)網(wǎng)絡(luò)數(shù)據(jù)接收),就會(huì)阻塞當(dāng)前線程,讓出 CPU。 非阻塞 I/O,指用戶線程發(fā)起 I/O 請(qǐng)求的時(shí)候,如果數(shù)據(jù)還未準(zhǔn)備就緒(例如暫無(wú)網(wǎng)絡(luò)數(shù)據(jù)接收),也不會(huì)阻塞當(dāng)前線程,可以繼續(xù)執(zhí)行后續(xù)的任務(wù)。
可以發(fā)現(xiàn),這里的阻塞和非阻塞其實(shí)是指用戶線程是否會(huì)被阻塞。
同步 I/O 和異步 I/O。按照上文,我們可以得知這就是根據(jù) I/O 響應(yīng)方式不同而劃分的。
同步 I/O,指用戶線程發(fā)起 I/O 請(qǐng)求的時(shí)候,數(shù)據(jù)是有的,那么將進(jìn)行步驟2(實(shí)際 I/O 讀寫(xiě),即數(shù)據(jù)從內(nèi)核緩存拷貝到用戶空間),這個(gè)過(guò)程用戶線程是要等待著拷貝完成。 異步 I/O,指用戶線程發(fā)起 I/O 請(qǐng)求的時(shí)候,數(shù)據(jù)是有的,那么將進(jìn)行步驟2(實(shí)際 I/O 讀寫(xiě),即數(shù)據(jù)從內(nèi)核緩存拷貝到用戶空間),拷貝的過(guò)程中不需要用戶線程等待,用戶線程可以去執(zhí)行其它邏輯,等內(nèi)核將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間后,用戶線程會(huì)得到一個(gè)“通知”。
再仔細(xì)思考下,在 I/O 場(chǎng)景下同步和異步說(shuō)的其實(shí)是內(nèi)核的實(shí)現(xiàn),因?yàn)榭截惖膱?zhí)行者是內(nèi)核,一種是同步將數(shù)據(jù)拷貝到用戶空間,用戶線程是需要等著的。一個(gè)是通過(guò)異步的方式,用戶線程不用等,在拷貝完之后,內(nèi)核會(huì)調(diào)用指定的回調(diào)函數(shù)。
如果不理解上面,就只需記住:
同步I/O:指的是用戶線程會(huì)需要等待步驟 2 執(zhí)行完畢。 異步I/O:指的是用戶線程不需要等待步驟 2 執(zhí)行。
好了,如果以上的概念你都已經(jīng)理解了的話,那么平日里我們所說(shuō)的同步阻塞I/O,同步非阻塞I/O等其實(shí)就是把上面的兩個(gè)步驟合起來(lái)看,應(yīng)該不難理解。
我再簡(jiǎn)單的總結(jié)一下,關(guān)于 I/O 的阻塞、非阻塞、同步、異步:
阻塞和非阻塞指的是發(fā)起 I/O 請(qǐng)求后,用戶線程狀態(tài)的不同,阻塞I/O在數(shù)據(jù)未準(zhǔn)備就緒的時(shí)候會(huì)阻塞當(dāng)前用戶線程,而非阻塞 I/O 會(huì)立馬返回一個(gè)錯(cuò)誤,不會(huì)阻塞當(dāng)前用戶線程。 同步和異步是指,內(nèi)核的 I/O 拷貝實(shí)現(xiàn),當(dāng)數(shù)據(jù)準(zhǔn)備就緒后,需要將內(nèi)核空間的數(shù)據(jù)拷貝至用戶空間,如果是同步 I/O 那么用戶線程會(huì)等待拷貝的完成,而異步 I/O則這個(gè)拷貝過(guò)程用戶線程該干嘛可以去干嗎,當(dāng)內(nèi)核拷貝完畢之后會(huì)“通知”用戶線程。
最后
要注意,不同場(chǎng)景下同一個(gè)名詞意義可能不同。我這篇關(guān)于同步、異步、阻塞、非阻塞這幾個(gè)概念是基于 I/O 場(chǎng)景下講的。
個(gè)人能力有限,不知道有沒(méi)有講清楚,如有疑問(wèn)可以留言區(qū)哈。
