靈魂拷問:JS 為什么是單線程?
前述
本文主要對js與瀏覽器之間的進(jìn)程與線程開展,業(yè)務(wù)之余豐富一些認(rèn)知~~~
進(jìn)程與線程
什么是進(jìn)程
我們都知道,CPU是計算機的核心,承擔(dān)所有的計算任務(wù)官網(wǎng)說法,進(jìn)程是CPU資源分配的最小單位字面意思就是進(jìn)行中的程序,可以將它理解為一個可以獨立運行且擁有自己的資源空間的任務(wù)程序``進(jìn)程包括運行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源CPU可以有很多進(jìn)程,我們的電腦每打開一個軟件就會產(chǎn)生一個或多個進(jìn)程,為什么電腦運行的軟件多就會卡,是因為CPU給每個進(jìn)程分配資源空間,但是一個CPU一共就那么多資源,分出去越多,越卡,每個進(jìn)程之間是相互獨立的,CPU在運行一個進(jìn)程時,其他的進(jìn)程處于非運行狀態(tài),CPU使用 時間片輪轉(zhuǎn)調(diào)度算法[1] 來實現(xiàn)同時運行多個進(jìn)程
什么是線程
線程是CPU調(diào)度的最小單位線程是建立在進(jìn)程的基礎(chǔ)上的一次程序運行單位,通俗點解釋線程就是程序中的一個執(zhí)行流,一個進(jìn)程可以有多個線程一個進(jìn)程中只有一個執(zhí)行流稱作單線程,即程序執(zhí)行時,所走的程序路徑按照連續(xù)順序排下來,前面的必須處理好,后面的才會執(zhí)行一個進(jìn)程中有多個執(zhí)行流稱作多線程,即在一個程序中可以同時運行多個不同的線程來執(zhí)行不同的任務(wù), 也就是說允許單個程序創(chuàng)建多個并行執(zhí)行的線程來完成各自的任務(wù)
進(jìn)程和線程的區(qū)別
進(jìn)程是操作系統(tǒng)分配資源的最小單位,線程是程序執(zhí)行的最小單位一個進(jìn)程由一個或多個線程組成,線程可以理解為是一個進(jìn)程中代碼的不同執(zhí)行路線進(jìn)程之間相互獨立,但同一進(jìn)程下的各個線程間共享程序的內(nèi)存空間(包括代碼段、數(shù)據(jù)集、堆等)及一些進(jìn)程級的資源(如打開文件和信號)調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多
多進(jìn)程和多線程
多進(jìn)程:多進(jìn)程指的是在同一個時間里,同一個計算機系統(tǒng)中如果允許兩個或兩個以上的進(jìn)程處于運行狀態(tài)。多進(jìn)程帶來的好處是明顯的,比如大家可以在網(wǎng)易云聽歌的同時打開編輯器敲代碼,編輯器和網(wǎng)易云的進(jìn)程之間不會相互干擾多線程:多線程是指程序中包含多個執(zhí)行流,即在一個程序中可以同時運行多個不同的線程來執(zhí)行不同的任務(wù),也就是說允許單個程序創(chuàng)建多個并行執(zhí)行的線程來完成各自的任務(wù)
JS為什么是單線程
JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。那么,為什么JavaScript不能有多個線程呢?這樣能提高效率啊。JS的單線程,與它的用途有關(guān)。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復(fù)雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內(nèi)容,另一個線程刪除了這個節(jié)點,這時瀏覽器應(yīng)該以哪個線程為準(zhǔn)?還有人說js還有Worker線程,對的,為了利用多核CPU的計算能力,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個線程,但是子線程是完 全受主線程控制的,而且不得操作DOM 所以,這個標(biāo)準(zhǔn)并沒有改變JavaScript是單線程的本質(zhì)了解了進(jìn)程和線程之后,接下來看看瀏覽器解析,瀏覽器之間也是有些許差距的,不過大致是差不多的,下文我們皆用市場占有比例最大的Chrome為例
瀏覽器
瀏覽器是多進(jìn)程的
作為前端,免不了和瀏覽器打交道,瀏覽器是多進(jìn)程的,拿Chrome來說,我們每打開一個Tab頁就會產(chǎn)生一個進(jìn)程,我們使用Chrome打開很多標(biāo)簽頁不關(guān),電腦會越來越卡,不說其他,首先就很耗CPU
瀏覽器包含哪些進(jìn)程
Browser進(jìn)程
瀏覽器的主進(jìn)程(負(fù)責(zé)協(xié)調(diào)、主控),該進(jìn)程只有一個 負(fù)責(zé)瀏覽器界面顯示,與用戶交互。如前進(jìn),后退等 負(fù)責(zé)各個頁面的管理,創(chuàng)建和銷毀其他進(jìn)程 將渲染(Renderer)進(jìn)程得到的內(nèi)存中的Bitmap(位圖),繪制到用戶界面上 網(wǎng)絡(luò)資源的管理,下載等 第三方插件進(jìn)程
每種類型的插件對應(yīng)一個進(jìn)程,當(dāng)使用該插件時才創(chuàng)建 GPU進(jìn)程
該進(jìn)程也只有一個,用于3D繪制等等 渲染進(jìn)程(重)
即通常所說的瀏覽器內(nèi)核(Renderer進(jìn)程,內(nèi)部是多線程) 每個Tab頁面都有一個渲染進(jìn)程,互不影響 主要作用為頁面渲染,腳本執(zhí)行,事件處理等
為什么瀏覽器要多進(jìn)程
我們假設(shè)瀏覽器是單進(jìn)程,那么某個Tab頁崩潰了,就影響了整個瀏覽器,體驗有多差同理如果插件崩潰了也會影響整個瀏覽器當(dāng)然多進(jìn)程還有其它的諸多優(yōu)勢,不過多闡述瀏覽器進(jìn)程有很多,每個進(jìn)程又有很多線程,都會占用內(nèi)存這也意味著內(nèi)存等資源消耗會很大,有點拿空間換時間的意思到此可不只是為了讓我們理解為何Chrome運行時間長了電腦會卡,哈哈,第一個重點來了
簡述渲染進(jìn)程Renderer(重)
頁面的渲染,JS的執(zhí)行,事件的循環(huán),都在渲染進(jìn)程內(nèi)執(zhí)行,所以我們要重點了解渲染進(jìn)程渲染進(jìn)程是多線程的,我們來看渲染進(jìn)程的一些常用較為主要的線程
渲染進(jìn)程Renderer的主要線程

GUI渲染線程
負(fù)責(zé)渲染瀏覽器界面,解析HTML,CSS,構(gòu)建DOM樹和RenderObject樹,布局和繪制等
解析html代碼(HTML代碼本質(zhì)是字符串)轉(zhuǎn)化為瀏覽器認(rèn)識的節(jié)點,生成DOM樹,也就是DOM Tree 解析css,生成CSSOM(CSS規(guī)則樹) 把DOM Tree 和CSSOM結(jié)合,生成Rendering Tree(渲染樹) 當(dāng)我們修改了一些元素的顏色或者背景色,頁面就會重繪(Repaint)
當(dāng)我們修改元素的尺寸,頁面就會回流(Reflow)
當(dāng)頁面需要Repaing和Reflow時GUI線程執(zhí)行,繪制頁面
回流(Reflow)比重繪(Repaint)的成本要高,我們要盡量避免Reflow和Repaint
GUI渲染線程與JS引擎線程是互斥的
當(dāng)JS引擎執(zhí)行時GUI線程會被掛起(相當(dāng)于被凍結(jié)了) GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執(zhí)行
JS引擎線程
JS引擎線程就是JS內(nèi)核,負(fù)責(zé)處理Javascript腳本程序(例如V8引擎)
JS引擎線程負(fù)責(zé)解析Javascript腳本,運行代碼
JS引擎一直等待著任務(wù)隊列中任務(wù)的到來,然后加以處理
瀏覽器同時只能有一個JS引擎線程在運行JS程序,所以js是單線程運行的 一個Tab頁(renderer進(jìn)程)中無論什么時候都只有一個JS線程在運行JS程序 GUI渲染線程與JS引擎線程是互斥的,js引擎線程會阻塞GUI渲染線程
就是我們常遇到的JS執(zhí)行時間過長,造成頁面的渲染不連貫,導(dǎo)致頁面渲染加載阻塞(就是加載慢) 例如瀏覽器渲染的時候遇到 <script>標(biāo)簽,就會停止GUI的渲染,然后js引擎線程開始工作,執(zhí)行里面的js代碼,等js執(zhí)行完畢,js引擎線程停止工作,GUI繼續(xù)渲染下面的內(nèi)容。所以如果js執(zhí)行時間太長就會造成頁面卡頓的情況
事件觸發(fā)線程
屬于瀏覽器而不是JS引擎,用來控制事件循環(huán),并且管理著一個事件隊列(task queue) 當(dāng)js執(zhí)行碰到事件綁定和一些異步操作(如setTimeOut,也可來自瀏覽器內(nèi)核的其他線程,如鼠標(biāo)點擊、AJAX異步請求等),會走事件觸發(fā)線程將對應(yīng)的事件添加到對應(yīng)的線程中(比如定時器操作,便把定時器事件添加到定時器線程),等異步事件有了結(jié)果,便把他們的回調(diào)操作添加到事件隊列,等待js引擎線程空閑時來處理。 當(dāng)對應(yīng)的事件符合觸發(fā)條件被觸發(fā)時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理 因為JS是單線程,所以這些待處理隊列中的事件都得排隊等待JS引擎處理
定時觸發(fā)器線程
setInterval與setTimeout所在線程瀏覽器定時計數(shù)器并不是由JavaScript引擎計數(shù)的(因為JavaScript引擎是單線程的,如果處于阻塞線程狀態(tài)就會影響記計時的準(zhǔn)確) 通過單獨線程來計時并觸發(fā)定時(計時完畢后,添加到事件觸發(fā)線程的事件隊列中,等待JS引擎空閑后執(zhí)行),這個線程就是定時觸發(fā)器線程,也叫定時器線程 W3C在HTML標(biāo)準(zhǔn)中規(guī)定,規(guī)定要求 setTimeout中低于4ms的時間間隔算為4ms
異步http請求線程
在XMLHttpRequest在連接后是通過瀏覽器新開一個線程請求 將檢測到狀態(tài)變更時,如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將這個回調(diào)再放入事件隊列中再由JavaScript引擎執(zhí)行 簡單說就是當(dāng)執(zhí)行到一個http異步請求時,就把異步請求事件添加到異步請求線程,等收到響應(yīng)(準(zhǔn)確來說應(yīng)該是http狀態(tài)變化),再把回調(diào)函數(shù)添加到事件隊列,等待js引擎線程來執(zhí)行
后言
感興趣的小伙伴可以瀏覽下以下系列相關(guān)文章,后續(xù)會深入瀏覽器的解析渲染進(jìn)行展開(歡迎點贊+關(guān)注)
關(guān)于本文
作者:凌凌柒哦
https://juejin.cn/post/7137955193009733646
