JavaScript Weird Part (7) - 單執行緒、同步執行

在學習 JavaScript 時,必須要知道它是單執行緒 (single threaded) ,也就是單線程。

可以理解成它一次只做一件事情。瀏覽器無論什麼時候,都只有一個單線程在執行 JavaScript。

單執行緒

單執行緒的意思是一次執行一個指令,可是在使用瀏覽器的時候,瀏覽器只分配給 JavaScript 一個主執行緒(stack),用來執行任務(函式),但一次只能執行一個任務,這些任務形成一個事件佇列 (Queue) 排隊等候執行,

但是瀏覽器不只運作 JavaScript 這個程式而已。因為瀏覽器的內核是多線程的,它們藉由瀏覽器內核的控制保持同步,舉例來說有三個常駐線程:

  • JavaScript 引擎
  • GUI 渲染
  • 瀏覽器事件觸發

針對瀏覽器事件觸發,當一個事件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待 JavaScript 引擎的處理。
這些事件可來自 JavaScript 引擎當前執行的程式碼,如 setTimeout、也可來自瀏覽器內核如滑鼠點擊、Ajax 非同步請求等,但由於 JavaScript 的單線程關係,所有這些事件都得排隊等待 JavaScript 引擎處理

JavaScript 的單執行緒,與它的用途有關。作為瀏覽器指令碼語言,JavaScript 的主要用途是與使用者互動,以及操作 DOM。這就決定了他只能是單執行緒,否則會帶來很複雜的同步問題。比如,假定 JavaScript 同時有兩個執行緒,一個執行緒在某個 DOM 節點上新增內容,另一個執行緒刪除了這個節點,這時瀏覽器應該以哪個執行緒為準?

同步執行

同步執行和單執行緒有點類似,同步的意思是,對 JavaScript 來說,一次一個而不是一次兩個或三個。是程式碼中的一行,一次執行一行,而且是照順序的,程式碼會依照出現的順序,一次執行一行。

單執行緒如何執行非同步?

以上可以統整出兩個重點:

  1. 瀏覽器內核除了 JavaScript 的執行緒,還有其他同步執行的執行緒。
  2. setTimeouteventAjax…等無法預期執行時間的操作,都會先被丟到事件佇列排隊,等候執行,等到同步執行的程式碼執行完之後,才會去處理那些佇列中的任務。

可是如果 JavaScript 裡不存在非同步,只能一行一行、由上而下執行,那在同步執行下,如果上一行解析時間很長,那 setTimeoutevent callbackHttp request 這些無法預期觸發執行時間的操作,不就會塞車?對於使用者而言,這樣的使用者體驗很差。

所以單執行緒可以透過事件迴圈 (Event loop) 來實現這些事件,將這些耗時、無法預期執行時間的操作以非同步處理,等執行完同步的程式碼再來處理它們。執行 function 時,會依執行順序把 function 丟到 主執行緒中,等到主執行緒中的任務都執行完畢,才會將事件佇列中待執行的任務拉到主執行緒中執行,執行完畢、清空後,再到 事件佇列中查看是否還有待執行任務,這個查看的過程就稱為 Event Loop

小範例

以下的範例,不在 JavaScript 的奇怪部分課程裡。找了網路上其他人的範例,並且修改了細節,以便增進自己的理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
setTimeout(function() {
console.log("5sec");
}, 5000);
setTimeout(function() {
console.log("1sec");
}, 1000);
setTimeout(function() {
console.log("3sec");
}, 3000);

var now = Date.now();
while (true) {
if ((Date, now() > now + 2000)) {
break;
}
}
console.log("Nihao");
setTimeout(function() {
console.log("2sec");
}, 2000);
setTimeout(function() {
console.log("0sec");
}, 0);

這個範例流程可以分為兩條線解析 —

setTimeout:

  1. setTimeout 被先拉到旁邊開始倒數
  2. 倒數完畢,由 callback function 放到事件佇列

while 等待兩秒:

  1. 執行同步執行的程式碼
  2. 印出 Nihao

範例小結

setTimeoutsetInterval 等設定的等待時,例如 3 秒,並不能確保它真會在設定的時間到就馬上執行,只能夠確保它在大於等於 3sec 候才會執行。

因此以上的範例印出的順序及運作流程為:

  1. 5、1、3 秒拉出倒數(倒數完成後由 callback function 丟進事件佇列)
  2. while 等待 2 秒
  3. 印出 Nihao
  4. 2、0 秒拉出倒數(倒數完成後由 callback function 丟進事件佇列)
  5. 主執行緒空了,查看事件佇列
  6. 印出 1、3sec (5sec 還在倒數)
  7. 印出 while 下方的 0sec
  8. 印出 5sec
  9. 印出 2sec

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2020 CYC'S BLOG All Rights Reserved.

UV : | PV :