什么是同步 / 異步任務(wù)?
發(fā)出調(diào)用,立即得到結(jié)果是為同步付燥;否則異步锈至。
Nodejs 運行機制
根據(jù)上圖簡單總結(jié):
- 我們寫的 js 代碼交給 V8 處理
- 解析后的代碼,調(diào)用Node API
- 負責Node API的執(zhí)行担败。它將不同的任務(wù)分配給不同的線程,形成一個Event Loop(事件循環(huán)),以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給V8引擎
- V8引擎再將結(jié)果返回給用戶
個人理解:
node 啟動鼠渺,v8解析,執(zhí)行同步任務(wù)眷细;
異步任務(wù)通過node api(Node bindings)交給libuv執(zhí)行拦盹,注冊到對應的事件隊列
同步執(zhí)行完,進入事件循環(huán)
其中同步任務(wù)在v8執(zhí)行溪椎;異步任務(wù)系統(tǒng)內(nèi)核處理普舆,最后結(jié)果返回給v8,v8返回給應用
微任務(wù)執(zhí)行時機
Node
環(huán)境:微任務(wù)在事件循環(huán)的各個階段的 空隙(中間)執(zhí)行
瀏覽器:微任務(wù)在事件循環(huán)的宏任務(wù)執(zhí)行完后執(zhí)行
<------microTasks
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
| | <----- microTasks
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
| | <----- microTasks
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘
| | <----- microTasks
| |
| | ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
| | └───────────────┘
| | <----- microTasks
│ ┌──────────┴────────────┐
│ │ check │
│ └──────────┬────────────┘
| | <----- microTasks
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
Libuv庫負責Node API的執(zhí)行校读。它將不同的任務(wù)分配給不同的線程沼侣,形成一個事件循環(huán), 以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給V8引擎歉秫《曷澹可以簡單用下面這張圖來表示。
每一個I/O都需要一個回調(diào)函數(shù)——一旦執(zhí)行完便推到事件循環(huán)上用于執(zhí)行。
2019-03-06-12:49:不能停轧膘,繼續(xù)~
Event Loop
主線程從 ”任務(wù)隊列” 中讀取事件钞螟,這個過程是循環(huán)不斷的,所以整個的這種 運行機制 又稱為Event Loop
(事件循環(huán))谎碍。
渲染進程中的所有運行在主線程的任務(wù)都需要先添加到消息隊列中鳞滨,然后事件循環(huán)在按照順序執(zhí)行消息隊列的任務(wù)。比如:我點擊頁面的按鈕蟆淀,觸發(fā)一段 JS 邏輯拯啦,然后進行異步網(wǎng)絡(luò)請求。
瀏覽器進程:將鼠標點擊通過 IPC 機制通知到渲染進程的 I/O 線程扳碍;
I / O 線程:將鼠標點擊事件加入到消息隊列提岔;
主線程:當主線程執(zhí)行完當前的任務(wù)之后,通過事件循環(huán)笋敞,取出消息隊列的點擊事件執(zhí)行碱蒙;
JS 線程:執(zhí)行點擊事件的 JS 邏輯,將 JS 邏輯中的同步代碼加入執(zhí)行棧中執(zhí)行夯巷;
網(wǎng)絡(luò)進程:當遇到需要異步網(wǎng)絡(luò)請求時赛惩,主線程將異步網(wǎng)絡(luò)請求通過 IPC 通訊交給網(wǎng)絡(luò)進程去執(zhí)行,并繼續(xù)執(zhí)行執(zhí)行棧中的同步代碼(相當于同步代碼和異步請求在同時執(zhí)行)趁餐;
網(wǎng)絡(luò)進程:當異步網(wǎng)絡(luò)資源加載完成喷兼,網(wǎng)絡(luò)進程通過 IPC 通訊通知到通知到渲染進程,將異步網(wǎng)絡(luò)回調(diào)加入到消息隊列后雷;
主線程:主線程將同步代碼執(zhí)行完成了季惯,通過事件循環(huán)取出消息隊列中的異步網(wǎng)絡(luò)回調(diào)放入執(zhí)行棧接著執(zhí)行。
通過不斷循環(huán)去取出異步回調(diào)來執(zhí)行(存在一個執(zhí)行循環(huán)來檢測任務(wù)隊列是否有新的任務(wù))臀突,這個過程就是事件循環(huán)勉抓。
關(guān)鍵字:主線程(會形成執(zhí)行棧)、隊列任務(wù)候学、“主完異進”
其它相關(guān)
js本身執(zhí)行是單線程的藕筋,也就是說當前代碼執(zhí)行的時候,是會阻塞其他代碼執(zhí)行的梳码。
但是js的運行環(huán)境隐圾,譬如瀏覽器本身是多線程執(zhí)行的,包括javascript引擎線程掰茶,界面渲染線程暇藏,瀏覽器事件觸發(fā)線程,Http請求線程等濒蒋。
一道經(jīng)典的前端面試題
for(var i = 0; i < 5; i ++) {
setTimeout(function() {
console.log(i);
}, 0);
}
輸出5個5叨咖,為什么呢?
因為setTimeout的任務(wù)是異步的,js執(zhí)行棧(JS引擎中負責解釋和執(zhí)行JavaScript代碼的線程甸各,可以成為主線程)在執(zhí)行完js代碼后,才會去從消息隊列里面取消息焰坪、執(zhí)行消息趣倾,再取消息、再執(zhí)行某饰。當消息隊列為空時儒恋,就會等待直到消息隊列變成非空。
所以上面的代碼中黔漂,for循環(huán)執(zhí)行完畢之后诫尽,setTimeout里頭的回調(diào)函數(shù)才會被調(diào)用。那個時候i已經(jīng)變成了5炬守,因為放入了5個定時器牧嫉,所以會輸出5個5。
JavaScript是單線程執(zhí)行的减途,無法同時執(zhí)行多段代碼酣藻。當某一段代碼正在執(zhí)行的時候,所有后續(xù)的任務(wù)都必須等待鳍置,形成一個隊列辽剧。一旦當前任務(wù)執(zhí)行完畢,再從隊列中取出下一個任務(wù)税产,這也常被稱為 “阻塞式執(zhí)行”怕轿。所以一次鼠標點擊,或是計時器到達時間點辟拷,或是Ajax請求完成觸發(fā)了回調(diào)函數(shù)撞羽,這些事件處理程序或回調(diào)函數(shù)都不會立即運行,而是立即排隊梧兼,一旦線程有空閑就執(zhí)行放吩。假如當前 JavaScript線程正在執(zhí)行一段很耗時的代碼,此時發(fā)生了一次鼠標點擊羽杰,那么事件處理程序就被阻塞渡紫,用戶也無法立即看到反饋,事件處理程序會被放入任務(wù)隊列考赛,直到前面的代碼結(jié)束以后才會開始執(zhí)行惕澎。如果代碼中設(shè)定了一個 setTimeout,那么瀏覽器便會在合適的時間颜骤,將代碼插入任務(wù)隊列唧喉,如果這個時間設(shè)為 0,就代表立即插入隊列,但不是立即執(zhí)行八孝,仍然要等待前面代碼執(zhí)行完畢董朝。所以 setTimeout 并不能保證執(zhí)行的時間,是否及時執(zhí)行取決于 JavaScript 線程是擁擠還是空閑干跛。
也就是說setTimeout只能保證在指定的時間過后將任務(wù)(需要執(zhí)行的函數(shù))插入隊列等候子姜,并不保證這個任務(wù)在什么時候執(zhí)行。執(zhí)行javascript的線程會在空閑的時候楼入,自行從隊列中取出任務(wù)然后執(zhí)行它哥捕。javascript通過這種隊列機制,給我們制造一個異步執(zhí)行的假象嘉熊。
部分引用來自:https://blog.csdn.net/qq_36995542/article/details/80007381