高效異步IO,按我理解起來應(yīng)該不光要達(dá)到程序運(yùn)行時的高效,當(dāng)然也要達(dá)到開發(fā)效率的高效,其中還包括很重要一點(diǎn)就是質(zhì)量,對于很多從未做過異步IO的人來說,初次嘗試異步IO肯定會碰到不少困難,因為不光是對編程能力的考驗,也需要開發(fā)者對操作系統(tǒng)的IO操作有相當(dāng)?shù)牧私猓ㄋ臋C(jī)制和部分原理。異步IO中也有高效低效之分,但主要還是要看具體的應(yīng)用到底需要什么樣機(jī)制。比如大家熟知的select就是個非常通用且跨平臺的方法,由于select中需要把大量的時間花在維護(hù)IO句柄上,導(dǎo)致其效率大打折扣,一般來說,對于小并發(fā)的異步IO操作,比如普通的客戶端或者是小并發(fā)量的服務(wù)器,它的效率可能也足夠了。關(guān)于select的效率問題其實從各平臺上對于FD_SETSIZE的定義就能看出一些來,在windows平臺上,F(xiàn)D_SETSIZE是64,在Linux平臺上是1024,也就是說,對于平臺提供商來說也不指望他們提供的select能給你多大的并發(fā)吞吐能力,但由于select的簡單和普及,其應(yīng)用面還是很廣,很多時候確實也不需要太多的并發(fā)量。其實說到高效異步IO的開發(fā),我們也說了,不光是考慮到程序運(yùn)行時的效率,還要考慮開發(fā)的效率和軟件的質(zhì)量,說到這里,其實select這么個簡單的機(jī)制有時候用起來也不那么簡單,而且還會出很多錯誤。
說到select重復(fù)的維護(hù)句柄的開銷,其實也是有解決方法的,好的解決方法效率會提高很多,但是重復(fù)工作還是要做的,比如當(dāng)select返回結(jié)果是0,或者當(dāng)能確定不需要增減IO句柄時,我們可以簡單的把原先保存的FD_SET副本重新寫入,這樣可以減少重新生成FD_SET的開銷,內(nèi)存復(fù)制效率顯然高于隊列的一次次遍歷,這是顯而易見的。當(dāng)然對于大并發(fā)量的IO操作來說,這種方法對于效率的提高也是很有限的,說到底即使采用異步IO效率也并不一定就高,還要取決于很多其它因素。其中很重要一點(diǎn)大家別忘記把偵聽端口設(shè)置成異步的,雖然不設(shè)置的話程序運(yùn)行后好像沒有什么不正常,但光從表面上就可以看到CPU占用率明顯偏高,當(dāng)然還會有一些其它問題產(chǎn)生,這個問題比較復(fù)雜,這里不作描述了。并發(fā)操作的效率還取決于“連接-->斷開->連接”的頻度,頻繁的“連接-->斷開->連接”也會產(chǎn)生不小的開銷,當(dāng)然這些開銷和之后要講的真正實現(xiàn)業(yè)務(wù)操作需要的開銷比起來其實要小得多,而且也是有很多方法可以規(guī)避的,對于采用不同的異步IO實現(xiàn)方法也是很不同的,效率差異會非常大。對我們來說,歸根結(jié)底是根據(jù)不同的模型采用不同的方法來提高效率,作為一個異步IO的前端“發(fā)生器”應(yīng)該盡可能避免在自己的工作上消耗過多的CPU資源,而是盡可能把CPU資源讓給具體的業(yè)務(wù)實現(xiàn)者。
異步I/O中的Edge-Triggered和Level-Triggered是非常重要的概念;Edge-Triggered字面上理解就是指“邊界觸發(fā)”,說的是當(dāng)狀態(tài)變化的時候觸發(fā),以后如果狀態(tài)一直沒有變化或沒有重新要求系統(tǒng)給出通知,將不再通知應(yīng)用程序;Level-Triggered是指“狀態(tài)觸發(fā)”,說的是在某種狀態(tài)下觸發(fā),如果一直在這種狀態(tài)下就一直觸發(fā)。兩種觸發(fā)方式各有用途,應(yīng)根據(jù)不同的應(yīng)用采用不同的觸發(fā)方式。select一般默認(rèn)采用的是Level-Triggered,而EPoll既可以采用Edge-Triggered,也可以采用Level-Triggered,默認(rèn)是Level-Triggered,而MS的CPIO按這種定義來說應(yīng)該屬于Edge-Triggered。對于已經(jīng)封裝好的異步I/O架構(gòu)來說,具體采用哪種方式其實無傷大雅,因為無論采用哪種方式,都需要在內(nèi)部都實現(xiàn)正確了,并且讓使用者不再關(guān)心這種具體的觸發(fā)方式為好。
void EPollReactor::NotifyMeWrite(SOCKET handle, SvcHandler *handler)
{
DIAMON_ASSERT(handle != INVALID_SOCKET);
EPoll_Mod_Handle_Events(handle, EPOLLOUT/* | EPOLLET*/);
}
上述代碼中,就是在每次寫完數(shù)據(jù)后需要異步I/O框架再通知應(yīng)用程序關(guān)于寫完,其中EPoll_Mod_Handle_Events函數(shù)告訴系統(tǒng)給handle注冊上EPOLLOUT消息,這樣當(dāng)handle完成寫操作后,系統(tǒng)將通知框架寫完消息,是否加上EPOLLET完全取決于框架同應(yīng)用者之間的協(xié)議,其實本質(zhì)上就是框架對外提供的接口和調(diào)用約定。在Diamon::ACE中,采用的是Level-Triggered方式。
void IOCPReactor::NotifyMeWrite(SOCKET handle, SvcHandler *handler)
{
DIAMON_ASSERT(handle != INVALID_SOCKET);
IOCPSvcHandler *iocphandler = (IOCPSvcHandler *)handler;
iocphandler->event_ &= (~IOCP_EVENT_READ);
iocphandler->event_ |= IOCP_EVENT_WRITE;
}
對比一下,CPIO中的NotifyMeWrite做的不是通知系統(tǒng),而是告訴異步I/O框架自己接下來應(yīng)該處理寫事件了,而對系統(tǒng)的觸發(fā)工作完全是交給WSAWrite...之類的函數(shù)來完成的。想想啊,每次調(diào)用WSAWrite...不正是對系統(tǒng)說,我給這個handle注冊一下寫事件啊,下次還通知我,你可以試試不調(diào)用WSAWrite...了,下次肯定收不到寫通知了。
同步問題是高效異步IO設(shè)計中非常重要但又常被忽視的問題(更不能濫用),不好的同步方法有時會限制應(yīng)用層的使用。我曾經(jīng)犯過這么一個錯誤:select操作和FD_SET的操作是必須順序進(jìn)行的,否則會產(chǎn)生不可預(yù)期的后果。我們知道,在寫數(shù)據(jù)操作后往往需要將寫通知告訴應(yīng)用層,因此需要在寫操作后往寫FD_SET中設(shè)置handle,但是寫方法的調(diào)用和select方法的調(diào)用可能是在2個線程中,當(dāng)我一開始遇到這個問題的時候,我只簡單的在select的外圍加了一對mutex操作,當(dāng)應(yīng)用層在收到讀通知中直接調(diào)用寫方法沒有問題,因為這時寫方法的調(diào)用和select調(diào)用處在一個線程中(肯定是順序執(zhí)行的),表面上看這個問題確實是解決了,但實際上卻隱藏了一些很嚴(yán)重的問題,當(dāng)寫方法是在其他的“業(yè)務(wù)處理器”中,比如另一個線程中,那么這種調(diào)用就會導(dǎo)致FD_SET的操作和select操作有同時進(jìn)行的可能,而當(dāng)這種情況發(fā)生后,顯而易見程序是不可能正常運(yùn)行了。而對于應(yīng)用程序來說,唯一解決這個問題的方法,就是要知道在select的外圍的mutex對象,然后在自己調(diào)用寫方法的外圍再包上一對這個mutex操作,雖然解決了FD_SET操作和select操作同步的問題,但實際上卻把問題更復(fù)雜化了,比如:一、讓應(yīng)用程序知道本不需要,更不應(yīng)該讓它知道的邏輯,導(dǎo)致了應(yīng)用層開發(fā)的復(fù)雜性,甚至給應(yīng)用程序帶來由于錯誤使用導(dǎo)致更嚴(yán)重的問題;二、始終會存在某幾種情況會導(dǎo)致互鎖問題的產(chǎn)生。第二個問題可能會有點(diǎn)復(fù)雜,為了便于理解,我給大家解釋一下互鎖,我們考慮這么一種情況,有兩個任務(wù)分別使用mutexA和mutexB,任務(wù)1中使用的順序是...,mutexA.Lock(),...mutexB(),...,任務(wù)2中使用的順序是...,mutexB.Lock(),...mutexA(),...,當(dāng)任務(wù)1占用mutexA后等待mutexB前,任務(wù)2也剛好占用了mutexB在等待mutexA,這時候很明顯就制造了一個互鎖(交叉鎖)。第二個問題其實就是由這種調(diào)用下導(dǎo)致的某種互鎖情況。總之,我的這種不動腦子的解決問題的方法導(dǎo)致了嚴(yán)重的應(yīng)用層問題。
轉(zhuǎn)自: http://hippoweilin.mobile.spaces.live.com/arc.aspx