摘自廖雪峰教程
在IO編程一節(jié)中吻商,我們已經(jīng)知道掏颊,CPU的速度遠(yuǎn)遠(yuǎn)快于磁盤、網(wǎng)絡(luò)等IO艾帐。在一個線程中乌叶,CPU執(zhí)行代碼的速度極快,然而柒爸,一旦遇到IO操作准浴,如讀寫文件、發(fā)送網(wǎng)絡(luò)數(shù)據(jù)時捎稚,就需要等待IO操作完成乐横,才能繼續(xù)進(jìn)行下一步操作。這種情況稱為同步IO今野。
在IO操作的過程中葡公,當(dāng)前線程被掛起,而其他需要CPU執(zhí)行的代碼就無法被當(dāng)前線程執(zhí)行了腥泥。
因?yàn)橐粋€IO操作就阻塞了當(dāng)前線程匾南,導(dǎo)致其他代碼無法執(zhí)行啃匿,所以我們必須使用多線程或者多進(jìn)程來并發(fā)執(zhí)行代碼蛔外,為多個用戶服務(wù)。每個用戶都會分配一個線程溯乒,如果遇到IO導(dǎo)致線程被掛起夹厌,其他用戶的線程不受影響。
多線程和多進(jìn)程的模型雖然解決了并發(fā)問題裆悄,但是系統(tǒng)不能無上限地增加線程。由于系統(tǒng)切換線程的開銷也很大,所以事镣,一旦線程數(shù)量過多驳规,CPU的時間就花在線程切換上了,真正運(yùn)行代碼的時間就少了艾君,結(jié)果導(dǎo)致性能嚴(yán)重下降采够。
由于我們要解決的問題是CPU高速執(zhí)行能力和IO設(shè)備的龜速嚴(yán)重不匹配,多線程和多進(jìn)程只是解決這一問題的一種方法冰垄。
另一種解決IO問題的方法是異步IO蹬癌。當(dāng)代碼需要執(zhí)行一個耗時的IO操作時,它只發(fā)出IO指令,并不等待IO結(jié)果逝薪,然后就去執(zhí)行其他代碼了隅要。一段時間后,當(dāng)IO返回結(jié)果時董济,再通知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模型需要一個消息循環(huán)藻雌,在消息循環(huán)中驯杜,主線程不斷地重復(fù)“讀取消息-處理消息”這一過程:
loop = get_event_loop()
whileTrue:? ?
? ? ? ? ?event = loop.get_event()? ?
? ? ? ? ?process_event(event)
消息模型其實(shí)早在應(yīng)用在桌面應(yīng)用程序中了。一個GUI程序的主線程就負(fù)責(zé)不停地讀取消息并處理消息顽频。所有的鍵盤、鼠標(biāo)等消息都被發(fā)送到GUI程序的消息隊(duì)列中,然后由GUI程序的主線程處理。
由于GUI線程處理鍵盤仗岖、鼠標(biāo)等消息的速度非常快檩电,所以用戶感覺不到延遲奄侠。某些時候烹卒,GUI線程在一個消息處理的過程中遇到問題導(dǎo)致一次消息處理時間過長牡整,此時谣辞,用戶會感覺到整個GUI程序停止響應(yīng)了迫皱,敲鍵盤凹炸、點(diǎn)鼠標(biāo)都沒有反應(yīng)舱痘。這種情況說明在消息模型中,處理一個消息必須非常迅速离赫,否則芭逝,主線程將無法及時處理消息隊(duì)列中的其他消息,導(dǎo)致程序看上去停止響應(yīng)渊胸。
消息模型是如何解決同步IO必須等待IO操作這一問題的呢旬盯?當(dāng)遇到IO操作時,代碼只負(fù)責(zé)發(fā)出IO請求翎猛,不等待IO結(jié)果胖翰,然后直接結(jié)束本輪消息處理,進(jìn)入下一輪消息處理過程切厘。當(dāng)IO操作完成后萨咳,將收到一條“IO完成”的消息,處理該消息時就可以直接獲取IO操作結(jié)果疫稿。
在“發(fā)出IO請求”到收到“IO完成”的這段時間里某弦,同步IO模型下,主線程只能掛起而克,但異步IO模型下靶壮,主線程并沒有休息,而是在消息循環(huán)中繼續(xù)處理其他消息员萍。這樣腾降,在異步IO模型下,一個線程就可以同時處理多個IO請求碎绎,并且沒有切換線程的操作螃壤。對于大多數(shù)IO密集型的應(yīng)用程序,使用異步IO將大大提升系統(tǒng)的多任務(wù)處理能力筋帖。
舉例:
UNIX有5種I/O模型,阻塞會發(fā)生在兩個階段上:
1.阻塞式I/O? ? 等待數(shù)據(jù)時阻塞? 數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間時阻塞
2.非阻塞式I/O 等待數(shù)據(jù)不阻塞,但是輪詢會占用cpu資源 數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間時阻塞
3.I/O復(fù)用? 考慮到輪詢占用cpu資源的問題,阻塞在選擇器上,減輕處理器負(fù)擔(dān)? 將數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間時阻塞
4.信號驅(qū)動式I/O 等待數(shù)據(jù)不阻塞,數(shù)據(jù)準(zhǔn)備好時通知接收數(shù)據(jù),將數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間時阻塞
以上四種或多或少均有阻塞現(xiàn)象存在,它們都是同步I/O模型
5.異步I/O 等待數(shù)據(jù)時不阻塞 將數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間時也不阻塞. 數(shù)據(jù)到了用戶空間以后才發(fā)信號,就像你在網(wǎng)上下了訂單,快遞員拿著你的快件站在你家門口才通知你開門簽收的樣子.
而在網(wǎng)上下了訂單,貨物到了離你家最近的自提點(diǎn),商城通知你去自提點(diǎn)取提貨.你專門抽出時間去提貨.這是4.信號驅(qū)動式I/O.因?yàn)樵趤砘刈蕴狳c(diǎn)的路上你其實(shí)是阻塞的.
例子2:
老張愛喝茶奸晴,廢話不說,煮開水日麸。
出場人物:老張寄啼,水壺兩把(普通水壺,簡稱水壺代箭;會響的水壺墩划,簡稱響水壺)。
1 老張把水壺放到火上嗡综,立等水開乙帮。(同步阻塞)
老張覺得自己有點(diǎn)傻
2 老張把水壺放到火上,去客廳看電視极景,時不時去廚房看看水開沒有察净。(同步非阻塞)
老張還是覺得自己有點(diǎn)傻驾茴,于是變高端了,買了把會響笛的那種水壺氢卡。水開之后沟涨,能大聲發(fā)出嘀~~~~的噪音。
3 老張把響水壺放到火上异吻,立等水開裹赴。(異步阻塞)
老張覺得這樣傻等意義不大
4 老張把響水壺放到火上,去客廳看電視诀浪,水壺響之前不再去看它了棋返,響了再去拿壺。(異步非阻塞)
老張覺得自己聰明了雷猪。
所謂同步異步睛竣,只是對于水壺而言。
普通水壺求摇,同步射沟;響水壺,異步与境。
雖然都能干活验夯,但響水壺可以在自己完工之后,提示老張水開了摔刁。這是普通水壺所不能及的挥转。
同步只能讓調(diào)用者去輪詢自己(情況2中),造成老張效率的低下共屈。
所謂阻塞非阻塞绑谣,僅僅對于老張而言。
立等的老張拗引,阻塞借宵;看電視的老張,非阻塞矾削。
情況1和情況3中老張就是阻塞的壤玫,媳婦喊他都不知道。雖然3中響水壺是異步的怔软,可對于立等的老張沒有太大的意義垦细。所以一般異步是配合非阻塞使用的,這樣才能發(fā)揮異步的效用挡逼。