關(guān)于同步和異步,我們先來看兩個(gè)例子。
const async=()=>{
console.log('async..',1)
let t=new Date();
while (true){
if(+new Date()-t>=2000){
console.log('async...',2)
break
}
}
console.log('async...',3)
}
async();
//async.. 1
//async... 2
//async... 3
順序執(zhí)行
const sync=()=>{
console.log('sync...',1);
setTimeout(()=>{
console.log('sync...',2)
},2000);
console.log('sync...',3)
}
sync();
//sync... 1
//sync... 3
//sync... 2
可能都知道JavaScript是單線程的,即同一時(shí)刻只能做一件事,如果有多個(gè)任務(wù),則需要排隊(duì)執(zhí)行,但是這樣同步執(zhí)行的效率低,如果一個(gè)任務(wù)長時(shí)間據(jù)有CPU,其他任務(wù)則需要等待,這無疑會(huì)浪費(fèi)資源,造成資源利用率低。為此,
JavaScript將任務(wù)的執(zhí)行分為兩種:
- 同步:后一個(gè)任務(wù)等待前一個(gè)任務(wù)結(jié)束,然后再執(zhí)行,程序的執(zhí)行順序與任務(wù)的排列順序是一致的、同步的;
- 異步:后一個(gè)任務(wù)不等前一個(gè)任務(wù)結(jié)束就執(zhí)行,程序的執(zhí)行順序與任務(wù)的排列順序是不一致的、異步的
需要了解的基本知識
進(jìn)程與線程
進(jìn)程(process):我們知道,程序運(yùn)行是需要系統(tǒng)資源的(CPU,內(nèi)存,I/O等)為了能使程序能夠并發(fā)執(zhí)行,并對并發(fā)執(zhí)行的程序加以描述和控制,從而引入進(jìn)程。是進(jìn)程實(shí)體的運(yùn)行過程,是系統(tǒng)進(jìn)行資源分配和調(diào)動(dòng)的獨(dú)立單位
線程(thread):通過引入線程,一個(gè)能獨(dú)立運(yùn)行的基本單位,作為操作系統(tǒng)調(diào)度和分派的基本單位。通過減少程序在并發(fā)進(jìn)行時(shí)所付出的時(shí)空開銷,從而提高程序并發(fā)執(zhí)行的程度,以及提高資源利用率和系統(tǒng)的吞吐量
進(jìn)程和線程的區(qū)別和關(guān)系:
- 進(jìn)程是操作系統(tǒng)分配資源的最小單位,線程是程序執(zhí)行的最小單位。
- 一個(gè)進(jìn)程由一個(gè)或多個(gè)線程組成,線程是一個(gè)進(jìn)程中代碼的不同執(zhí)行路線;
- 進(jìn)程之間相互獨(dú)立,但同一進(jìn)程下的各個(gè)線程之間共享程序的內(nèi)存空間(包括代碼段、數(shù)據(jù)集、堆等)及一些進(jìn)程級的資源(如打開文件和信號)。
- 調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多。
以工廠模式比喻,江南皮革廠老板苦惱產(chǎn)量提不上去,于是給車間主任(進(jìn)程)開會(huì)。
老板:你們幾個(gè)怎么搞得,怎么訂單(多任務(wù))多了,產(chǎn)量還跟不上了?
車間主任A:手底下這么多人,哪管的過來。訂單不好分配、進(jìn)度不無法跟蹤
車間主任B:A車間經(jīng)常跟我們車間搶物料(系統(tǒng)資源)
車間主任A:放***屁,你們的人上個(gè)月還搶我設(shè)備呢(系統(tǒng)資源)
老板:當(dāng)然你們說的都是客觀存在的因素,你們不會(huì)從手下挑選幾個(gè)有能力的人成立班組(線程)么?權(quán)利下放(獨(dú)立調(diào)度)
車間主任A,B:好好好,試試。
linux下查看進(jìn)程的常用命令
- ps
- top
Javascript 單線程
在ecma-262中并無與線程相關(guān)的內(nèi)容,其單線程/多線程主要依賴于其JavaScript引擎(解釋器)。
瀏覽器的進(jìn)程和線程
以Chrome為例,Chrome瀏覽器使用多個(gè)進(jìn)程來隔離不同的網(wǎng)頁。因此在Chrome中打開一個(gè)網(wǎng)頁相當(dāng)于起了一個(gè)進(jìn)程

多進(jìn)程的原因:
- 因?yàn)檫M(jìn)程之間相互獨(dú)立,一個(gè)瀏覽器選項(xiàng)卡無響應(yīng)不會(huì)影響其他的選項(xiàng)卡。
- 同樣因?yàn)檫M(jìn)程之間相互獨(dú)立,一個(gè)選項(xiàng)卡中如果惡意腳本,不會(huì)影響其他選項(xiàng)卡。例如,Chrome 瀏覽器可以對處理用戶輸入(如渲染器)的進(jìn)程,限制其文件訪問的權(quán)限。
GUI線程:渲染布局(HTML,CSS等)
JS引擎線程:1.解析、執(zhí)行JS 2.與GUI線程互斥 (因?yàn)橐媸菃尉€程的,所以Javascript是單線程的)
定時(shí)觸發(fā)器線程:setTimeout/setInterval
事件觸發(fā)線程:將滿足觸發(fā)條件的時(shí)間放入任務(wù)隊(duì)列
異步HTTP請求線程:XHR所在線程
單線程的原因(引用自瀏覽器進(jìn)程?線程?傻傻分不清楚!)
這是因?yàn)镴avascript這門腳本語言誕生的使命所致:JavaScript為處理頁面中用戶的交互,以及操作DOM樹、CSS樣式樹來給用戶呈現(xiàn)一份動(dòng)態(tài)而豐富的交互體驗(yàn)和服務(wù)器邏輯的交互處理。如果JavaScript是多線程的方式來操作這些UI DOM,則可能出現(xiàn)UI操作的沖突; 如果Javascript是多線程的話,在多線程的交互下,處于UI中的DOM節(jié)點(diǎn)就可能成為一個(gè)臨界資源,假設(shè)存在兩個(gè)線程同時(shí)操作一個(gè)DOM,一個(gè)負(fù)責(zé)修改一個(gè)負(fù)責(zé)刪除,那么這個(gè)時(shí)候就需要瀏覽器來裁決如何生效哪個(gè)線程的執(zhí)行結(jié)果。當(dāng)然我們可以通過鎖來解決上面的問題。但為了避免因?yàn)橐肓随i而帶來更大的復(fù)雜性,Javascript在最初就選擇了單線程執(zhí)行。
Node.js中的進(jìn)程和線程
當(dāng)一個(gè) Node.js 的應(yīng)用啟動(dòng)的同時(shí),它會(huì)啟動(dòng)如下模塊:
- 一個(gè)進(jìn)程
- 一個(gè)線程:單線程意味著在當(dāng)前進(jìn)程中同一時(shí)刻只有一個(gè)指令在執(zhí)行。
- 事件循環(huán)機(jī)制:盡管 JavaScript 是單線程的,但通過使用回調(diào),promises, async/await 等語法,基于事件循環(huán)將對操作系統(tǒng)的操作異步化,使得 Node 擁有異步非阻塞 IO 的特性。
- JS 引擎實(shí)例
- Node.js 實(shí)例
Node 運(yùn)行在單線程上,并且在事件循環(huán)中同一時(shí)刻只有一個(gè)進(jìn)程的任務(wù)被執(zhí)行,每次同一時(shí)刻只會(huì)執(zhí)行一段代碼(多段代碼不會(huì)同時(shí)執(zhí)行)。
只有一個(gè)js引擎在主線程上運(yùn)行。其他異步IO和事件驅(qū)動(dòng)相關(guān)的線程通過libuv來實(shí)現(xiàn)內(nèi)部的線程池和線程調(diào)度
為什么Node.js也是單線程呢?
因?yàn)镴avaScript起初是運(yùn)行在瀏覽器端的...你懂的。
Node.js如何實(shí)現(xiàn)并行:
它通過事件輪詢(event loop)來實(shí)現(xiàn)并行操作,因此我們要避免阻塞操作,
Node.js可以多進(jìn)程/多線程么?
利用
child_process模塊 fork子進(jìn)程,實(shí)現(xiàn)多進(jìn)程利用
cluster模塊fork子進(jìn)程--Master-Worker,實(shí)現(xiàn)多進(jìn)程

- 使用第三方包node-threads-a-gogo 實(shí)現(xiàn)多線程
前端的異步場景
- 定時(shí)器
- 網(wǎng)絡(luò)請求
- 事件綁定
- ES6 Promise
定時(shí)器
再回到我們的代碼
console.log('sync...',1);
setTimeout(()=>{
console.log('sync...',2)
},2000);
console.log('sync...',3)
流程分析:
- 主程序調(diào)用棧
- 調(diào)用webAPI-
setTimeout - 定時(shí)器線程計(jì)數(shù)2s
- 事件觸發(fā)線程將定時(shí)器時(shí)間放入任務(wù)隊(duì)列
- 主線程通過Event Loop遍歷任務(wù)隊(duì)列執(zhí)行異步任務(wù)
console.log()
console.time('test')
const test=()=>{
let t=+new Date();
while (true){
if(+new Date()-t>=5000){
break;
}
}
setTimeout(()=>{
console.log('setTimeout...')
},2000)
}
test();
console.timeEnd('test');
注意:
- 定時(shí)任務(wù)可能不會(huì)按時(shí)執(zhí)行,延遲原因(等待同步任務(wù)、等待CPU加載)
- 定時(shí)器嵌套5次之后最小間隔不能低于4ms
應(yīng)用場景:
- 防抖
- 節(jié)流
- 倒計(jì)時(shí)
- 動(dòng)畫(存在丟幀)
案例分析
for(var i=1;i<=10;i++){
setTimeout(function() {
console.log(i)
},1000*i)
}
在這個(gè)案例中,我們期望每隔1s,依次打印i的值。但實(shí)際結(jié)果卻是每隔1s重復(fù)打印11。
問題的原因在于:
- 定時(shí)器需要等待同步任務(wù)執(zhí)行完成,那么i的值為11。
-
var是全局作用域
修復(fù)方案:
//使用閉包,保留當(dāng)前的作用域
for(var i=1;i<=10;i++){
(i=>{
setTimeout(function() {
console.log(i)
},1000*i)
})(i)
}
//使用let 保留當(dāng)前的作用域
for(let i=1;i<=10;i++){
setTimeout(function() {
console.log(i)
},1000*i)
}
待學(xué)習(xí)
當(dāng)我們談?wù)?cluster 時(shí)我們在談?wù)撌裁?上)