nodejs的特點
nodejs 具有事件驅動和非阻塞I/O的特點。
事件驅動是指nodejs把每一個任務當成事件來處理。
非阻塞I/O是指nodejs遇到I/O任務時,會從線程池調度單獨的線程處理I/O操作,不會阻塞主線程。
事件循環原理
Node.js 在主線程里維護了一個事件隊列,當接到請求后,就將該請求作為一個事件放入這個隊列中,然后繼續接收其他請求。
當主線程空閑時(沒有請求接入時),就開始循環事件隊列,檢查隊列中是否有要處理的事件,這時要分兩種情況:
如果是非 I/O 任務,就親自處理,並通過回調函數返回到上層調用;
如果是 I/O 任務,就從 線程池 中拿出一個線程來處理這個事件,並指定回調函數,然后繼續循環隊列中的其他事件。
當線程中的 I/O 任務完成以后,就執行指定的回調函數,並把這個完成的事件放到事件隊列的尾部,等待事件循環,當主線程再次循環到該事件時,就直接處理並返回給上層調用。
流程圖
每次循環的六個階段
timers階段:這個階段執行定時器隊列中的回調,如 setTimeout()
和 setInterval()
。
I/O callbacks: 這個階段執行幾乎所有的回調。但是不包括close事件,定時器和setImmediate()
的回調。
idle, prepare: 這個階段僅在內部使用,可以不必理會。
poll: 等待新的I/O事件,node在一些特殊情況下會阻塞在這里。
check: setImmediate()
的回調會在這個階段執行。
close callbacks: 例如socket.on('close', ...)
這種close事件的回調。
下面我們來按照代碼第一次進入libuv引擎后的順序來詳細解說這些階段:
當個v8引擎將js代碼解析后傳入libuv引擎后,循環首先進入poll階段。
poll階段的執行邏輯如下:
先查看poll queue中是否有事件,有事件就按先進先出的順序依次執行回調。
當queue為空時,會檢查是否有setImmediate()的callback,如果有就進入check階段執行這些callback。
當queue為空時,同時也會檢查是否有到期的timer,如果有,就把這些到期的timer的callback按照調用順序放到timer queue中,之后循環會進入timer階段執行queue中的 callback。
這兩者的順序是不固定的,收到代碼運行的環境的影響。
如果兩者的queue都是空的,那么loop會在poll階段停留,直到有一個i/o事件返回,循環會進入i/o callback階段並立即執行這個事件的callback。
值得注意的是,poll階段在執行poll queue中的回調時實際上不會無限的執行下去。
有兩種情況poll階段會終止執行poll queue中的下一個回調:1.所有回調執行完畢。2.執行數超過了node的限制。
特別感謝:
https://www.cnblogs.com/onepixel/p/7143769.html