進程與線程
一個程序中至少有一個進程,而一個進程中至少有一個線程
- 進程是運行中的程序,線程是進程內部的一個執行序列
- 進程是資源分配的單元,線程是執行單元
- 進程間切換代價大,線程間切換代價小
- 進程擁有的資源多,線程擁有的資源少
- 多個線程共享進程的資源
如:
工廠的資源 -> 系統分配的內存(獨立的一塊內存)
工廠之間的相互獨立 -> 進程之間相互獨立
多個工人協作完成任務 -> 多個線程在進程中協作完成任務
工廠內有一個或多個工人 -> 一個進程由一個或多個線程組成
工人之間共享空間 -> 同一進程下的各個線程之間共享程序的內存空間(包括代碼段、數據集、堆等)
瀏覽器是多進程的!!!
瀏覽器的進程:
- Browser進程:瀏覽器的主進程(負責協調、主控),只有一個
- 負責瀏覽器界面顯示,與用戶交互。如前進后退
- 負責各個頁面的管理,創建和銷毀其他進程
- 將Renderer進程得到的內存中的Bitmap,會知道用戶界面上
- 網絡資源的管理,下載等
- 第三方插件進程:每種類型的插件對應一個進程,僅當使用插件時才創建
- GPU進程:最多一個,用於3D繪制等
- 瀏覽器渲染進程(瀏覽器內核)(Renderer進程內部是多線程的):默認每個Tab頁面一個進程,互不影響。
- 頁面渲染、腳本執行、事件處理
重點:瀏覽器內核(渲染進程)
渲染進程是多線程的
包含:
- GUI渲染線程
- 負責渲染瀏覽器界面,解析HTML,CSS,構建DOM樹和RenderObject樹,布局和繪制等。
- 當界面需要重繪(Repaint)或由於某種操作引發回流(reflow)時,該線程就會執行
- 注意,GUI渲染線程與JS引擎線程是互斥的,當JS引擎執行時GUI線程會被掛起(相當於被凍結了),GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執行
- JS引擎線程
- 也稱為JS內核,負責處理Javascript腳本程序。(例如V8引擎)
- JS引擎線程負責解析Javascript腳本,運行代碼。
- JS引擎一直等待着任務隊列中任務的到來,然后加以處理,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序
- 同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞。
- 事件觸發線程
- 歸屬於瀏覽器而不是JS引擎,用來控制事件循環(可以理解,JS引擎自己都忙不過來,需要瀏覽器另開線程協助)
- 當JS引擎執行代碼塊如setTimeOut時(也可來自瀏覽器內核的其他線程,如鼠標點擊、AJAX異步請求等),會將對應任務添加到事件線程中
- 當對應的事件符合觸發條件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理
- 注意,由於JS的單線程關系,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執行)
- 定時器觸發線程
- 傳說中的setInterval與setTimeout所在線程
- 瀏覽器定時計數器並不是由JavaScript引擎計數的,(因為JavaScript引擎是單線程的, 如果處於阻塞線程狀態就會影響記計時的准確)
- 因此通過單獨線程來計時並觸發定時(計時完畢后,添加到事件隊列中,等待JS引擎空閑后執行)
- 注意,W3C在HTML標准中規定,規定要求setTimeout中低於4ms的時間間隔算為4ms。
- 異步http請求線程
- 在XMLHttpRequest在連接后是通過瀏覽器新開一個線程請求
- 將檢測到狀態變更時,如果設置有回調函數,異步線程就產生狀態變更事件,將這個回調再放入事件隊列中。再由JavaScript引擎執行。
單線程與多線程
單線程
所謂單線程(主線程),指在JS引擎中負責解釋和執行JavaScript代碼的線程只有一個。
多線程
如瀏覽器的渲染進程:
- GUI渲染線程
- JS引擎線程
- 事件觸發線程
- 定時器觸發線程
- 異步http請求線程
同步和異步
同步:如果在函數返回的時候,調用者就能夠得到預期結果(即拿到了預期的返回值或者看到了預期的效果),那么這個函數就是同步的
例如:
console.log('我是第一件事');
console.log('我是第二件事');
1.這兩個函數都是同步的
2.這段代碼是同步的
3.若有一個函數執行時間很長,后面的函數只能等待這個函數執行完才能執行(同步是阻塞的)
異步
先來看段代碼:
console.log('我是第一件事');
setTimeout(function () {
console.log('我突然有事,晚點再做第二件事');
},1000)
console.log('我是第三件事');
//我是第一件事
//我是第三件事
//我突然有事,晚點再做第二件事
這段代碼實現的就是異步
1.執行console.log('我是第一件事')
2.setTimeout異步函數跳過
3.執行console.log('我是第三件事')
4.js引擎空閑1秒后將異步函數推入事件隊列-->執行console.log('我突然有事,晚點再做第二件事')
補充:setTimeout和setInterval接受兩個參數,第一個參數為函數。第二個為時間(毫秒),及js引擎空閑幾秒后將其推入事件隊列。
再來一個:
console.log(0,'第一');
for (let i = 0;i<3;i++){
setTimeout(function(){
console.log(i,'第三');
},2000)
console.log(i,'第二');
};
//0 "第一"
//0 "第二"
//1 "第二"
//2 "第二"
//兩秒之后
//0 "第三"
//1 "第三"
//2 "第三"
補充:
- 異步機制是瀏覽器的兩個或兩個以上的常駐線程共同完成的
- 如:異步請求是由兩個常駐線程:JS執行線程和事件觸發線程共同完成的,JS的執行線程發起異步請求(這時瀏覽器會開一條新的HTTP請求線程來執行請求,這時JS的任務已完成,繼續執行線程隊列中剩下的其他任務),然后在未來的某一時刻事件觸發線程監視到之前的發起的HTTP請求已完成,它就會把完成事件插入到JS執行隊列的尾部等待JS處理。
- 如:定時觸發(settimeout和setinterval)是由瀏覽器的定時器線程執行的定時計數,然后在定時時間把定時處理函數的執行請求插入到JS執行隊列的尾端
消息隊列與事件循環
如上圖所示:
-
左邊的棧存儲的是同步任務,就是那些能立即執行、不耗時的任務,如變量和函數的初始化、事件的綁定等等那些不需要回調函數的操作都可歸為這一類。
-
右邊的堆用來存儲聲明的變量、對象。
-
下面的隊列就是消息隊列,一旦某個異步任務有了響應就會被推入隊列中。如用戶的點擊事件、瀏覽器收到服務的響應和setTimeout中待執行的事件,每個異步任務都和回調函數相關聯。
-
JS引擎線程用來執行棧中的同步任務,當所有同步任務執行完畢后,棧被清空,然后讀取消息隊列中的一個待處理任務,並把相關回調函數壓入棧中,單線程開始執行新的同步任務。
-
JS引擎線程從消息隊列中讀取任務是不斷循環的,每次棧被清空后,都會在消息隊列中讀取新的任務,如果沒有新的任務,就會等待,直到有新的任務,這就叫事件循環。
以Ajax異步請求為例:
實例
最后來個大的:
setTimeout(function(){
for(var i = 0; i < 100000000; i++){}
console.log('timer a');
}, 0)
for(var j = 0; j < 5; j++){
console.log(j);
}
setTimeout(function(){
console.log('timer b');
}, 0)
function waitFiveSeconds(){
var now = (new Date()).getTime();
while(((new Date()).getTime() - now) < 5000){}
console.log('finished waiting');
}
document.addEventListener('click', function(){
console.log('click');
})
console.log('click begin');
waitFiveSeconds();
//0
//1
//2
//3
//4
//click begin
//finished waiting
//timer a
//timer b
//click
再說一下:setTimeout(fn,0);是js引擎空閑后立即插入隊列,並不是立即執行
1.第一個setTimeout異步函數跳過
2.執行for循環輸出0,1,2,3,4
3.第二個setTimeout異步函數跳過
4.執行console.log('click begin')輸出 click begin
5.調用waitFiveSeconds()函數輸出finished waiting
6.waitFiveSeconds()執行完后同步任務結束,js引擎空閑會一次將異步函數插入隊列
7.輸出timer a;timer b;click