異步IO

摘自廖雪峰教程

在IO編程一節(jié)中,我們已經(jīng)知道,CPU的速度遠(yuǎn)遠(yuǎn)快于磁盤、網(wǎng)絡(luò)等IO。在一個(gè)線程中,CPU執(zhí)行代碼的速度極快,然而,一旦遇到IO操作,如讀寫文件、發(fā)送網(wǎng)絡(luò)數(shù)據(jù)時(shí),就需要等待IO操作完成,才能繼續(xù)進(jìn)行下一步操作。這種情況稱為同步IO

在IO操作的過程中,當(dāng)前線程被掛起,而其他需要CPU執(zhí)行的代碼就無法被當(dāng)前線程執(zhí)行了。

因?yàn)橐粋€(gè)IO操作就阻塞了當(dāng)前線程,導(dǎo)致其他代碼無法執(zhí)行,所以我們必須使用多線程或者多進(jìn)程來并發(fā)執(zhí)行代碼,為多個(gè)用戶服務(wù)。每個(gè)用戶都會(huì)分配一個(gè)線程,如果遇到IO導(dǎo)致線程被掛起,其他用戶的線程不受影響。

多線程和多進(jìn)程的模型雖然解決了并發(fā)問題,但是系統(tǒng)不能無上限地增加線程。由于系統(tǒng)切換線程的開銷也很大,所以,一旦線程數(shù)量過多,CPU的時(shí)間就花在線程切換上了,真正運(yùn)行代碼的時(shí)間就少了,結(jié)果導(dǎo)致性能嚴(yán)重下降。

由于我們要解決的問題是CPU高速執(zhí)行能力和IO設(shè)備的龜速嚴(yán)重不匹配,多線程和多進(jìn)程只是解決這一問題的一種方法。

另一種解決IO問題的方法是異步IO。當(dāng)代碼需要執(zhí)行一個(gè)耗時(shí)的IO操作時(shí),它只發(fā)出IO指令,并不等待IO結(jié)果,然后就去執(zhí)行其他代碼了。一段時(shí)間后,當(dāng)IO返回結(jié)果時(shí),再通知CPU進(jìn)行處理。

可以想象如果按普通順序?qū)懗龅拇a實(shí)際上是沒法完成異步IO的:

“do_some_code()

f = open('/path/to/file', 'r')

r = f.read() #<==線程停在此處等待IO操作結(jié)果

do_some_code(r)#IO操作

完成后線程才能繼續(xù)執(zhí)行:do_some_code(r)

所以,同步IO模型的代碼是無法實(shí)現(xiàn)異步IO模型的。

異步IO模型需要一個(gè)消息循環(huán),在消息循環(huán)中,主線程不斷地重復(fù)“讀取消息-處理消息”這一過程:

loop = get_event_loop()

whileTrue:? ?

? ? ? ? ?event = loop.get_event()? ?

? ? ? ? ?process_event(event)

消息模型其實(shí)早在應(yīng)用在桌面應(yīng)用程序中了。一個(gè)GUI程序的主線程就負(fù)責(zé)不停地讀取消息并處理消息。所有的鍵盤、鼠標(biāo)等消息都被發(fā)送到GUI程序的消息隊(duì)列中,然后由GUI程序的主線程處理。

由于GUI線程處理鍵盤、鼠標(biāo)等消息的速度非常快,所以用戶感覺不到延遲。某些時(shí)候,GUI線程在一個(gè)消息處理的過程中遇到問題導(dǎo)致一次消息處理時(shí)間過長,此時(shí),用戶會(huì)感覺到整個(gè)GUI程序停止響應(yīng)了,敲鍵盤、點(diǎn)鼠標(biāo)都沒有反應(yīng)。這種情況說明在消息模型中,處理一個(gè)消息必須非常迅速,否則,主線程將無法及時(shí)處理消息隊(duì)列中的其他消息,導(dǎo)致程序看上去停止響應(yīng)。

消息模型是如何解決同步IO必須等待IO操作這一問題的呢?當(dāng)遇到IO操作時(shí),代碼只負(fù)責(zé)發(fā)出IO請求,不等待IO結(jié)果,然后直接結(jié)束本輪消息處理,進(jìn)入下一輪消息處理過程。當(dāng)IO操作完成后,將收到一條“IO完成”的消息,處理該消息時(shí)就可以直接獲取IO操作結(jié)果。

在“發(fā)出IO請求”到收到“IO完成”的這段時(shí)間里,同步IO模型下,主線程只能掛起,但異步IO模型下,主線程并沒有休息,而是在消息循環(huán)中繼續(xù)處理其他消息。這樣,在異步IO模型下,一個(gè)線程就可以同時(shí)處理多個(gè)IO請求,并且沒有切換線程的操作。對于大多數(shù)IO密集型的應(yīng)用程序,使用異步IO將大大提升系統(tǒng)的多任務(wù)處理能力。

舉例:

UNIX有5種I/O模型,阻塞會(huì)發(fā)生在兩個(gè)階段上:

1.阻塞式I/O? ? 等待數(shù)據(jù)時(shí)阻塞? 數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間時(shí)阻塞

2.非阻塞式I/O 等待數(shù)據(jù)不阻塞,但是輪詢會(huì)占用cpu資源 數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間時(shí)阻塞

3.I/O復(fù)用? 考慮到輪詢占用cpu資源的問題,阻塞在選擇器上,減輕處理器負(fù)擔(dān)? 將數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間時(shí)阻塞

4.信號驅(qū)動(dòng)式I/O 等待數(shù)據(jù)不阻塞,數(shù)據(jù)準(zhǔn)備好時(shí)通知接收數(shù)據(jù),將數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間時(shí)阻塞

以上四種或多或少均有阻塞現(xiàn)象存在,它們都是同步I/O模型

5.異步I/O 等待數(shù)據(jù)時(shí)不阻塞 將數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間時(shí)也不阻塞. 數(shù)據(jù)到了用戶空間以后才發(fā)信號,就像你在網(wǎng)上下了訂單,快遞員拿著你的快件站在你家門口才通知你開門簽收的樣子.

而在網(wǎng)上下了訂單,貨物到了離你家最近的自提點(diǎn),商城通知你去自提點(diǎn)取提貨.你專門抽出時(shí)間去提貨.這是4.信號驅(qū)動(dòng)式I/O.因?yàn)樵趤砘刈蕴狳c(diǎn)的路上你其實(shí)是阻塞的.


例子2:

老張愛喝茶,廢話不說,煮開水。

出場人物:老張,水壺兩把(普通水壺,簡稱水壺;會(huì)響的水壺,簡稱響水壺)。

1 老張把水壺放到火上,立等水開。(同步阻塞)

老張覺得自己有點(diǎn)傻

2 老張把水壺放到火上,去客廳看電視,時(shí)不時(shí)去廚房看看水開沒有。(同步非阻塞)

老張還是覺得自己有點(diǎn)傻,于是變高端了,買了把會(huì)響笛的那種水壺。水開之后,能大聲發(fā)出嘀~~~~的噪音。

3 老張把響水壺放到火上,立等水開。(異步阻塞)

老張覺得這樣傻等意義不大

4 老張把響水壺放到火上,去客廳看電視,水壺響之前不再去看它了,響了再去拿壺。(異步非阻塞)

老張覺得自己聰明了。

所謂同步異步,只是對于水壺而言。

普通水壺,同步;響水壺,異步。

雖然都能干活,但響水壺可以在自己完工之后,提示老張水開了。這是普通水壺所不能及的。

同步只能讓調(diào)用者去輪詢自己(情況2中),造成老張效率的低下。

所謂阻塞非阻塞,僅僅對于老張而言。

立等的老張,阻塞;看電視的老張,非阻塞。

情況1和情況3中老張就是阻塞的,媳婦喊他都不知道。雖然3中響水壺是異步的,可對于立等的老張沒有太大的意義。所以一般異步是配合非阻塞使用的,這樣才能發(fā)揮異步的效用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容