好久沒寫東西了,過了一段鹹魚生活,無意中想起了脈脈上面一句話: 始終保持自己的競爭力。所以,繼續開寫! 一般的JavaScript源碼看的已經沒啥意思了,我也不會寫什麼xx入門新手教程,最終決定還是啃原來的硬骨頭,從外層libuv => node => v8一步步實現原有的目標吧。 libuv核心還 ...
好久沒寫東西了,過了一段鹹魚生活,無意中想起了脈脈上面一句話: 始終保持自己的競爭力。所以,繼續開寫!
一般的JavaScript源碼看的已經沒啥意思了,我也不會寫什麼xx入門新手教程,最終決定還是啃原來的硬骨頭,從外層libuv => node => v8一步步實現原有的目標吧。
libuv核心還是事件輪詢,前幾天從頭到尾看了一遍官網的文檔,對此有了一些更深的理解。
(雖然現在開發用的mac,但是為了銜接前面的文章,所以代碼仍舊以windows系統為基礎,反正差別也不大)
首先看一眼官網給的圖:
理論上輪詢都是一個無盡迴圈,所以不用在意loop alive問題。
上圖中,udpate loop time、Run due timers兩塊內容我已經在別的博客中講解過,這裡就懶得發傳送門了。
有兩個簡單的概念需要稍微提一下,libuv中有兩個抽象概念貫穿整個框架:handle、request。其中handle生命周期較長,且有自己回調方法的一個事務,比如說TCP的handle會處理每一個TCP連接,並觸發connection事件。request屬於handle中一個生命周期短,且簡單的行為,比如向文件進行讀、寫等等。
這一篇主要看一下接下來剩餘的部分,由於性質不太一樣,所以並不會按順序依次分析,而是從易到難,且源碼會做大量簡化,有興趣的人可以自己去看。
事件輪詢方法源碼精煉如下:
int uv_run(uv_loop_t *loop, uv_run_mode mode) { // ... while (r != 0 && loop->stop_flag == 0) { // update loop time uv_update_time(loop); // run due timers uv__run_timers(loop); // call pending callbacks ran_pending = uv_process_reqs(loop); // run idle handles uv_idle_invoke(loop); // run prepare handles uv_prepare_invoke(loop); // poll的阻塞時間處理 timeout = 0; if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT) timeout = uv_backend_timeout(loop); // poll for I/O if (pGetQueuedCompletionStatusEx) uv__poll(loop, timeout); else uv__poll_wine(loop, timeout); // run check handles uv_check_invoke(loop); // call close callbacks uv_process_endgames(loop); } // ... return r; }
Call close callbacks
這類回調比較特殊,官網是這麼解釋的: Close callbacks are called. If a handle was closed by calling uv_close()
it will get the close callback called.
簡單來講,就是僅在為了關閉一個handle,調用uv_close方法中所帶的callback會被認為是一個close callbacks。在使用node的時候,所有的操作(比如fs.readFile)不可主動取消,所以輪詢中這一步在JS層面是感知不到的。
作用上相當於vue鉤子函數中的destroy,由於觸發是在輪詢的最後一步,適合做一些收尾的工作,比如關閉文件描述符等等。
源碼中體現如下,首先是uv_close:
void uv_close(uv_handle_t* handle, uv_close_cb cb) { // 很多代碼... case UV_PREPARE: uv_prepare_stop((uv_prepare_t*)handle); uv__handle_closing(handle); uv_want_endgame(loop, handle); return; }
uv_close方法除了做關閉handle的本職工作,在最後都會調用一個uv_want_endgame方法收尾,這個方法是一個靜態方法。
INLINE static void uv_want_endgame(uv_loop_t* loop, uv_handle_t* handle) { if (!(handle->flags & UV_HANDLE_ENDGAME_QUEUED)) { handle->flags |= UV_HANDLE_ENDGAME_QUEUED; handle->endgame_next = loop->endgame_handles; loop->endgame_handles = handle; } }
內容十分簡單,將handle插入到endgame_handles這個鏈表的表頭。
最後,只需要看一眼uv_process_endgames即可。
INLINE static void uv_process_endgames(uv_loop_t* loop) { uv_handle_t* handle; while (loop->endgame_handles) { handle = loop->endgame_handles; loop->endgame_handles = handle->endgame_next; handle->flags &= ~UV_HANDLE_ENDGAME_QUEUED; switch (handle->type) { case UV_TCP: uv_tcp_endgame(loop, (uv_tcp_t*) handle); break; // ... } } }
也很簡潔明瞭,不停的取出endgame_handles鏈表中的handle,依次調用不同的callbacks即可。
Run idle hanldes、Run prepare handles、Run check handles
這三個雖然名字不一樣,但是主要作用類似,只是在調用順序上有所不同。
由於Poll for I/O是一個比較特殊的操作,所以這裡提供prepare、check兩個鉤子函數可以在這個事務前後進行一些別的調用,大可以用vue的鉤子函數created、mounted來幫助理解。
idle除去調用較早,也影響poll for I/O這個操作的阻塞時間timeout,官網原文: If there are any idle handles active, the timeout is 0.正常情況下事件輪詢會根據情況計算一個阻塞時間timout來決定poll for I/O操作的時間。
這裡用一個C++例子來證明調用順序,忽略上面的巨集,直接看main函數,特別簡單!!!
#include <iostream> #include "uv.h" using namespace std; void idle_callback(uv_idle_t* idle); void prepare_callback(uv_prepare_t* prepare); void check_callback(uv_check_t* check); #define RUN_HANDLE(type) \ do { \ uv_##type##_t type; \ uv_##type##_init(loop, &type); \ uv_##type##_start(&type, type##_callback); \ } while(0) #define CALLBACK(type) \ do { \ cout << "Run " << #type << " handles" << endl; \ uv_##type##_stop(type); \ } while(0) #define OPEN(PATH, callback) \ do { \ uv_fs_t req; \ uv_fs_open(loop, &req, PATH, O_RDONLY, 0, callback); \ uv_fs_req_cleanup(&req); \ } while(0) void idle_callback(uv_idle_t* idle) { CALLBACK(idle); } void prepare_callback(uv_prepare_t* prepare) { CALLBACK(prepare); } void check_callback(uv_check_t* check) { CALLBACK(check); } void on_open(uv_fs_t* req) { cout << "poll for I/O" << endl; } int main(int argc, const char * argv[]) { auto loop = uv_default_loop(); RUN_HANDLE(check); RUN_HANDLE(prepare); RUN_HANDLE(idle); OPEN("/Users/feilongpang/workspace/i.js", on_open); uv_run(loop, UV_RUN_DEFAULT); uv_loop_close(loop); return 0; }
執行的時候還發現了一個問題,如果不提供一個I/O操作,Run check handles那一步是會直接跳過,所以手動加了一個open操作。
可以看到,我特意調整了callback的添加順序,但是輸出依然是:
所以,代碼確實是按照官網示例所給的圖順序來執行。
剩下兩個poll for I/O、pending callbacks留到下一篇講吧。