目錄
async await机制
协程
单页面线程
Webworker线程
首頁 web前端 js教程 深入理解JavaScript的並發模型與事件循環機制

深入理解JavaScript的並發模型與事件循環機制

Nov 27, 2019 pm 04:08 PM
javascript settimeout 協程 執行緒

我們知道JS語言是串列執行、阻塞式、事件驅動的,那麼它又是怎麼支援並發處理資料的呢?

深入理解JavaScript的並發模型與事件循環機制

"單執行緒"語言

#在瀏覽器實作中,每個單頁都是一個獨立進程,其中包含了JS引擎、GUI介面渲染、事件觸發、定時觸發器、非同步HTTP請求等多個執行緒。

行程(Process)是作業系統CPU等資源分配的最小單位,是程式的執行實體,是執行緒的容器。
執行緒(Thread)是作業系統能夠進行運算調度的最小單位,一條執行緒指的是一個單一順序在進程中的控制流。

因此我們可以說JS是"單執行緒"式的語言,程式碼只能按照單一順序進行串行執行,並在執行完成前阻塞其他程式碼。

【相關課程推薦:JavaScript影片教學

#JS資料結構

深入理解JavaScript的並發模型與事件循環機制

#如上圖所示為JS的幾種重要資料結構:

 ● 堆疊(Stack):用於JS的函數巢狀調用,後進先出,直到堆疊被清空。

 ● 堆(Heap):用於儲存大塊資料的記憶體區域,如物件。

 ● 佇列(Queue):用於事件循環機制,先進先出,直到佇列為空。 事件循環

我們的經驗告訴我們JS是可以並發執行的,例如定時任務、並發AJAX請求,那這些是怎麼完成的呢?其實這些都是JS在用單線程模擬多線程完成的。 深入理解JavaScript的並發模型與事件循環機制

如上圖所示,JS串列執行主執行緒任務,當遇到非同步任務如定時器時,將其放入事件佇列中,在主執行緒任務執行完畢後,再去事件佇列中遍歷取出隊首任務執行,直到佇列為空。

全部執行完成後,會有主監控進程,持續偵測佇列是否為空,如果不為空,則繼續事件循環。 setTimeout定時任務

定時任務

setTimeout(fn, timeout)

會先交給瀏覽器的計時器模組,等延遲時間到了,再將事件放入到事件隊列裡,等主線程執行結束後,如果隊列中沒有其他任務,則會被立即處理,而如果還有沒有執行完成的任務,則需要等前面的任務都執行完成才會被執行。因此setTimeout的第2個參數是最少延遲時間,而非等待時間。

當我們預期到一個操作會很繁重耗時又不想阻塞主執行緒的執行時,會使用立即執行任務:
setTimeout(fn, 0);
登入後複製

特殊場景1:最小延遲為1ms

然而考慮這麼一段程式碼會怎麼執行:<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">setTimeout(()=&gt;{console.log(5)},5) setTimeout(()=&gt;{console.log(4)},4) setTimeout(()=&gt;{console.log(3)},3) setTimeout(()=&gt;{console.log(2)},2) setTimeout(()=&gt;{console.log(1)},1) setTimeout(()=&gt;{console.log(0)},0)</pre><div class="contentsignin">登入後複製</div></div>了解完事件佇列機制,你的答案應該是0,1,2,3,4,5 ,然而答案卻是

1,0,2,3,4,5

,這個是因為瀏覽器的實作機制是最小間隔為1ms。 <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">// https://github.com/nodejs/node/blob/v8.9.4/lib/timers.js#L456 if (!(after &gt;= 1 &amp;&amp; after </pre><div class="contentsignin">登入後複製</div></div>瀏覽器以32位元bit來儲存延時,若大於

2^32-1 ms(24.8天)

,導致溢位會立刻執行。

特殊場景2:最小延遲為4ms

計時器的巢狀呼叫超過4層時,會導致最小間隔為4ms:

var i=0;
function cb() {
    console.log(i, new Date().getMilliseconds());
    if (i <blockquote>可以看到前4層也不是標準的立刻執行,在第4層後間隔明顯變大到4ms以上:</blockquote><pre class="brush:php;toolbar:false">0 667
1 669
2 670
3 672
4 676
5 681
6 685
登入後複製

Timers can be nested; after five such nested timers, however, the interval is forced to be at least four milliseconds.

特殊場景3:瀏覽器節流


為了優化後台tab的載入佔用資源,瀏覽器對後台未啟動的頁面中定時器延遲限制為1s。

對追蹤型腳本,如Google分析等,在目前頁面,依然是4ms的延時限制,而後台tabs為10s。 setInterval定時任務

此時,我們會知道,setInterval會在每個定時器延時時間到了後,將會一個新的事件fn放入事件佇列,如果前面的任務執行太久,我們會看到連續的fn事件被執行而感覺不到時間預設間隔。

因此,我們要盡量避免使用setInterval,改用setTimeout來模擬迴圈計時任務。 睡眠函數

###JS一直缺少休眠的語法,借助ES6新的語法,我們可以模擬這個功能,但同樣的這個方法因為借助了setTimeout也無法保證準確的睡眠延遲:###
function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  })
}
// 使用
async function test() {
    await sleep(3000);
}
登入後複製

async await机制

async函数是Generator函数的语法糖,提供更方便的调用和语义,上面的使用可以替换为:

function* test() {
    yield sleep(3000);
}
// 使用
var g = test();
test.next();
登入後複製

但是调用使用更加复杂,因此一般我们使用async函数即可。但JS时如何实现睡眠函数的呢,其实就是提供一种执行时的中间状态暂停,然后将控制权移交出去,等控制权再次交回时,从上次的断点处继续执行。因此营造了一种睡眠的假象,其实JS主线程还可以在执行其他的任务。

Generator函数调用后会返回一个内部指针,指向多个异步任务的暂停点,当调用next函数时,从上一个暂停点开始执行。

协程

协程(coroutine)是指多个线程互相协作,完成异步任务的一种多任务异步执行的解决方案。他的运行流程:

 ● 协程A开始执行

 ● 协程A执行到一半,进入暂停,执行权转移到协程B

 ● 协程B在执行一段时间后,将执行权交换给A

 ● 协程A恢复执行

可以看到这也就是Generator函数的实现方案。

宏任务和微任务

一个JS的任务可以定义为:在标准执行机制中,即将被调度执行的所有代码块。

我们上面介绍了JS如何使用单线程完成异步多任务调用,但我们知道JS的异步任务分很多种,如setTimeout定时器、Promise异步回调任务等,它们的执行优先级又一样吗?

答案是不。JS在异步任务上有更细致的划分,它分为两种:

宏任务(macrotask)包含:

 ● 执行的一段JS代码块,如控制台、script元素中包含的内容。

 ● 事件绑定的回调函数,如点击事件。

 ● 定时器创建的回调,如setTimeout和setInterval。

微任务(microtask)包含:

 ● Promise对象的thenable函数。

 ● Nodejs中的process.nextTick函数。

 ● JS专用的queueMicrotask()函数。

深入理解JavaScript的並發模型與事件循環機制

宏任务和微任务都有自身的事件循环机制,也拥有独立的事件队列(Event Queue),都会按照队列的顺序依次执行。但宏任务和微任务主要有两点区别:

1、宏任务执行完成,在控制权交还给主线程执行其他宏任务之前,会将微任务队列中的所有任务执行完成。

2、微任务创建的新的微任务,会在下一个宏任务执行之前被继续遍历执行,直到微任务队列为空。

浏览器的进程和线程

浏览器是多进程式的,每个页面和插件都是一个独立的进程,这样可以保证单页面崩溃或者插件崩溃不会影响到其他页面和浏览器整体的稳定运行。

它主要包括:

1、主进程:负责浏览器界面显示和管理,如前进、后退,新增、关闭,网络资源的下载和管理。

2、第三方插件进程:当启用插件时,每个插件独立一个进程。

3、GPU进程:全局唯一,用于3D图形绘制。

4、Renderer渲染进程:每个页面一个进程,互不影响,执行事件处理、脚本执行、页面渲染。

单页面线程

浏览器的单个页面就是一个进程,指的就是Renderer进程,而进程中又包含有多个线程用于处理不同的任务,主要包括:

1、GUI渲染线程:负责HTML和CSS的构建成DOM树,渲染页面,比如重绘。

2、JS引擎线程:JS内核,如Chrome的V8引擎,负责解析执行JS代码。

3、事件触发线程:如点击等事件存在绑定回调时,触发后会被放入宏任务事件队列。

4、定时触发器线程:setTimeout和setInterval的定时计数器,在时间到达后放入宏任务事件队列。

5、异步HTTP请求线程:XMLHTTPRequest请求后新开一个线程,等待状态改变后,如果存在回调函数,就将其放入宏任务队列。

需要注意的是,GUI渲染进程和JS引擎进程互斥,两者只会同时执行一个。主要的原因是为了节流,因为JS的执行会可能多次改变页面,页面的改变也会多次调用JS,如resize。因此浏览器采用的策略是交替执行,每个宏任务执行完成后,执行GUI渲染,然后执行下一个宏任务。

Webworker线程

因为JS只有一个引擎线程,同时和GUI渲染线程互斥,因此在繁重任务执行时会导致页面卡住,所以在HTML5中支持了Webworker,它用于向浏览器申请一个新的子线程执行任务,并通过postMessage API来和worker线程通信。所以我们在繁重任务执行时,可以选择新开一个Worker线程来执行,并在执行结束后通信给主线程,这样不会影响页面的正常渲染和使用。

总结

1、JS是单线程、阻塞式执行语言。

2、JS通过事件循环机制来完成异步任务并发执行。

3、JS将任务细分为宏任务和微任务来提供执行优先级。

4、浏览器单页面为一个进程,包含的JS引擎线程和GUI渲染线程互斥,可以通过新开Web Worker线程来完成繁重的计算任务。

深入理解JavaScript的並發模型與事件循環機制

最后给大家出一个考题,可以猜下执行的输出结果来验证学习成果:

function sleep(ms) {
    console.log('before first microtask init');
    new Promise(resolve => {
        console.log('first microtask');
        resolve()
    })
    .then(() => {console.log('finish first microtask')});
    console.log('after first microtask init');
    return new Promise(resolve => {
          console.log('second microtask');
        setTimeout(resolve, ms);
    });
}
setTimeout(async () => {
    console.log('start task');
    await sleep(3000);
    console.log('end task');
}, 0);
setTimeout(() => console.log('add event'), 0);
console.log('main thread');
登入後複製

输出为:

main thread
start task
before first microtask init
first microtask
after first microtask init
second microtask
finish first microtask
add event
end task
登入後複製

本文来自 js教程 栏目,欢迎学习!

以上是深入理解JavaScript的並發模型與事件循環機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

golang函數與goroutine的父子關係 golang函數與goroutine的父子關係 Apr 25, 2024 pm 12:57 PM

Go中函數與goroutine存在父子關係,父goroutine創建子goroutine,子goroutine可以存取父goroutine的變數但不反之。建立子goroutine使用go關鍵字,子goroutine透過匿名函數或命名的函數執行。父goroutine可以透過sync.WaitGroup等待子goroutine完成,以確保在所有子goroutine完成之前不會退出程式。

並發和協程在Golang API設計中的應用 並發和協程在Golang API設計中的應用 May 07, 2024 pm 06:51 PM

並發和協程在GoAPI設計中可用於:高效能處理:同時處理多個請求以提高效能。非同步處理:使用協程非同步處理任務(例如傳送電子郵件),釋放主執行緒。流處理:使用協程高效處理資料流(例如資料庫讀取)。

Golang協程與 goroutine 的關係 Golang協程與 goroutine 的關係 Apr 15, 2024 am 10:42 AM

協程是並發執行任務的抽象概念,而goroutine是Go語言中的輕量級執行緒功能,實現了協程的概念。兩者聯繫密切,但goroutine資源消耗更低且由Go調度器管理。 goroutine廣泛用於實戰,如同時處理Web請求,提升程式效能。

C++並發程式設計:如何避免執行緒飢餓和優先反轉? C++並發程式設計:如何避免執行緒飢餓和優先反轉? May 06, 2024 pm 05:27 PM

為避免執行緒飢餓,可以使用公平鎖確保資源公平分配,或設定執行緒優先權。為解決優先權反轉,可使用優先權繼承,即暫時提高持有資源執行緒的優先權;或使用鎖的提升,即提升需要資源執行緒的優先權。

如何控制 Golang 協程的生命週期? 如何控制 Golang 協程的生命週期? May 31, 2024 pm 06:05 PM

控制Go協程的生命週期可以透過以下方式:建立協程:使用go關鍵字啟動新任務。終止協程:等待所有協程完成,使用sync.WaitGroup。使用通道關閉訊號。使用上下文context.Context。

C++並發程式設計:如何進行執行緒終止和取消? C++並發程式設計:如何進行執行緒終止和取消? May 06, 2024 pm 02:12 PM

C++中執行緒終止和取消機制包括:執行緒終止:std::thread::join()阻塞目前執行緒直到目標執行緒完成執行;std::thread::detach()從執行緒管理中分離目標執行緒。執行緒取消:std::thread::request_termination()請求目標執行緒終止執行;std::thread::get_id()取得目標執行緒ID,可與std::terminate()一起使用,立即終止目標執行緒。實戰中,request_termination()允許執行緒決定終止時機,join()確保在主線

Python asyncio 進階指南:從初學者到專家 Python asyncio 進階指南:從初學者到專家 Mar 04, 2024 am 09:43 AM

並發和非同步編程並發編程處理同時執行的多個任務,非同步編程是一種並發編程,其中任務不會阻塞線程。 asyncio是python中用於非同步程式設計的函式庫,它允許程式在不阻塞主執行緒的情況下執行I/O操作。事件循環asyncio的核心是事件循環,它監控I/O事件並調度相應的任務。當一個協程準備好時,事件循環會執行它,直到它等待I/O操作。然後,它會暫停協程並繼續執行其他協程。協程協程是可暫停和恢復執行的函數。 asyncdef關鍵字用於建立協程。協程使用await關鍵字等待I/O作業完成。 asyncio的基礎以下

Go語言中線程和進程的區別解析 Go語言中線程和進程的區別解析 Apr 03, 2024 pm 01:39 PM

Go語言中的進程和執行緒:進程:獨立運行的程式實例,擁有自己的資源和位址空間。執行緒:進程內的執行單元,共享行程資源和位址空間。特點:進程:開銷大,隔離性好,獨立調度。執行緒:開銷小,共享資源,內部調度。實戰案例:進程:隔離長時間運行的任務。線程:並發處理大量資料。

See all articles