為什么要異步I/O杈笔?
消除UI阻塞序六,快速響應(yīng)資源
JavaScript是單線程的,它與UI渲染共用一個(gè)線程悠就。所以在JavaScript執(zhí)行的時(shí)候千绪,UI渲染將處于停頓的狀態(tài),用戶體驗(yàn)較差梗脾。而異步請求可以在下載資源的時(shí)候荸型,JavaScript和UI渲染都同時(shí)執(zhí)行,消除UI阻塞炸茧,降低響應(yīng)資源需要的時(shí)間開銷瑞妇。
單線程與多線程的優(yōu)缺點(diǎn)
單線程:
優(yōu)點(diǎn):易于表達(dá),符合編程人員的思維方式
缺點(diǎn):阻塞IO梭冠,性能差踪宠。
多線程:
優(yōu)點(diǎn):充分利用多核CPU資源
缺點(diǎn):創(chuàng)建線程和執(zhí)行期線程上下文切換的開銷較大,在復(fù)雜業(yè)務(wù)中妈嘹,多線程經(jīng)常面臨鎖柳琢、狀態(tài)同步的問題
Node的資源分配解決方案
利用單線程,遠(yuǎn)離多線程的死鎖润脸、狀態(tài)同步等問題柬脸;利用異步I/O,讓單線程遠(yuǎn)離阻塞毙驯,更好的利CPU
異步I/O與非阻塞I/O:
I/O的阻塞與非阻塞:IO對于操作系統(tǒng)內(nèi)核而言倒堕,只有阻塞與非阻塞兩種方式。阻塞模式的I/O會(huì)造成應(yīng)用程序等待爆价,直到I/O完成垦巴。同時(shí)操作系統(tǒng)也支持將I/O操作設(shè)置為非阻塞模式,這時(shí)應(yīng)用程序的調(diào)用將可能在沒有拿到真正數(shù)據(jù)時(shí)就立即返回了铭段,為此應(yīng)用程序需要多次調(diào)用才能確認(rèn)I/O操作完全完成骤宣。這種重復(fù)調(diào)用判斷操作是否完成的技術(shù)叫做“輪詢”。
I/O的同步與異步:I/O的同步與異步出現(xiàn)在應(yīng)用程序中序愚。如果做阻塞I/O調(diào)用憔披,應(yīng)用程序等待調(diào)用的完成的過程就是一種同步狀況。相反,I/O為非阻塞模式時(shí)芬膝,應(yīng)用程序則是異步的望门。
Node的異步I/O模型:
事件循環(huán)
進(jìn)程啟動(dòng)時(shí),Node會(huì)創(chuàng)建一個(gè)類似while(true)的循環(huán)锰霜,判斷是否有事件需要處理
觀察者
觀察者是用來判斷是否有事件需要處理筹误。事件循環(huán)中有一到多個(gè)觀察者,判斷過程會(huì)向觀察者詢問是否有需要處理的事件癣缅。
這個(gè)過程類似于飯店的廚師與前臺(tái)服務(wù)員的關(guān)系纫事。廚師每做完一輪菜,就會(huì)向前臺(tái)服務(wù)員詢問是否有要做的菜所灸,如果有就繼續(xù)做,沒有的話就下班了炫七。這一過程中爬立,前臺(tái)服務(wù)員就相當(dāng)于觀察者,她收到的顧客點(diǎn)單就是回調(diào)函數(shù)万哪。
事件循環(huán)是一個(gè)典型的生產(chǎn)者/消費(fèi)者模型侠驯。異步I/O、網(wǎng)絡(luò)請求是生產(chǎn)者奕巍,而事件循環(huán)則從觀察者那里取出事件并處理吟策。
請求對象
以fs.open( )方法為例
```
fs.open = function(path, flags, mode, callback) {
//...
binding.open(pathModule._makeLong(path),
stringToFlags(flags),
mode,
callback);
};
```
這個(gè)函數(shù)的作用是根據(jù)指定的路徑和參數(shù)去打開一個(gè)文件,從而得到一個(gè)文件描述符的止,是后續(xù)所有I/O操作的初始操作檩坚。
整個(gè)調(diào)用過程:JavaScript -> Node核心模塊 -> C++內(nèi)建模塊 -> libuv系統(tǒng)調(diào)用
在uv_fs_open的調(diào)用過程中,Node.js創(chuàng)建了一個(gè)FSReqWrap請求對象诅福。從JavaScript傳入的參數(shù)和當(dāng)前方法都被封裝在這個(gè)請求對象中匾委,其中回調(diào)函數(shù)則被設(shè)置在這個(gè)對象的oncomplete_sym屬性上。
req_wrap->object_->Set(oncomplete_sym, callback);
對象包裝完畢后氓润,調(diào)用QueueUserWorkItem方法將這個(gè)FSReqWrap對象推入線程池中等待執(zhí)行赂乐。
```
QueueUserWorkItem(&uv_fs_thread_proc, req, WT_EXECUTELONGFUNCTION)
```
QueueUserWorkItem接受三個(gè)參數(shù),第一個(gè)是要執(zhí)行的方法咖气,第二個(gè)是方法的上下文挨措,第三個(gè)是執(zhí)行的標(biāo)志。
至此崩溪,由JavaScript層面發(fā)起的異步調(diào)用第一階段就此結(jié)束浅役。
執(zhí)行回調(diào)
組裝好請求對象,送入I/O線程池等待執(zhí)行伶唯,實(shí)際上完成了異步I/O的第一部分担租,回調(diào)通知是第二部分。
當(dāng)線程池中有可用線程的時(shí)候調(diào)用uv_fs_thread_proc方法執(zhí)行抵怎。該方法會(huì)根據(jù)傳入的類型調(diào)用相應(yīng)的底層函數(shù)奋救, ? ? 以uv_fs_open為例岭参,實(shí)際會(huì)調(diào)用到fs__open方法。調(diào)用完畢之后尝艘,會(huì)將獲取的結(jié)果設(shè)置在req->result上演侯。然后調(diào)用PostQueuedCompletionStatus通知我們的IOCP*對象操作已經(jīng)完成,并將線程歸還給線程池背亥。
```
PostQueuedCompletionStatus((loop)->iocp, 0, 0, &((req)->overlapped))
```
PostQueuedCompletionStatus方法的作用是向創(chuàng)建的IOCP上相關(guān)的線程通信秒际,線程根據(jù)執(zhí)行狀況和傳入的參數(shù)判定退出。
在這一過程中狡汉,每次事件循環(huán)會(huì)調(diào)用GetQueuedCompletionStatus()方法檢查線程池中是否有執(zhí)行完的請求娄徊,若有,會(huì)將請求對象加入到I/O觀察者的隊(duì)列中盾戴,將其作為事件處理寄锐。
I/O觀察者回調(diào)函數(shù)的行為就是取出請求對象的result屬性作為參數(shù),取出oncomplete_sym屬性作為方法尖啡,然后調(diào)用執(zhí)行橄仆,以此達(dá)到執(zhí)行回調(diào)函數(shù)的目的。
注:IOCP是windows下得異步I/O解決方案
總結(jié):JavaScript是單線程的衅斩,但Node本身其實(shí)是多線程的盆顾,除了用戶代碼無法并行執(zhí)行外,所有的I/O請求是可以并行執(zhí)行的畏梆。事件循環(huán)是Node異步I/O實(shí)現(xiàn)的核心您宪,Node通過事件驅(qū)動(dòng)的方式處理請求,使得其無須為每個(gè)請求創(chuàng)建額外的線程奠涌,省掉了創(chuàng)建和銷毀線程的開銷蚕涤。同時(shí)也應(yīng)為線程數(shù)較少,不受線程上下文切換的影響铣猩,維持了Node的高性能揖铜。