關(guān)于同步和異步证舟,我們先來看兩個例子。
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是單線程的缺谴,即同一時刻只能做一件事但惶,如果有多個任務(wù),則需要排隊執(zhí)行湿蛔,但是這樣同步執(zhí)行的效率低膀曾,如果一個任務(wù)長時間據(jù)有CPU,其他任務(wù)則需要等待阳啥,這無疑會浪費(fèi)資源添谊,造成資源利用率低。為此苫纤,
JavaScript將任務(wù)的執(zhí)行分為兩種:
- 同步:后一個任務(wù)等待前一個任務(wù)結(jié)束碉钠,然后再執(zhí)行纲缓,程序的執(zhí)行順序與任務(wù)的排列順序是一致的、同步的喊废;
- 異步:后一個任務(wù)不等前一個任務(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)程實體的運(yùn)行過程,是系統(tǒng)進(jìn)行資源分配和調(diào)動的獨(dú)立單位
線程(thread):通過引入線程惋增,一個能獨(dú)立運(yùn)行的基本單位叠殷,作為操作系統(tǒng)調(diào)度和分派的基本單位。通過減少程序在并發(fā)進(jìn)行時所付出的時空開銷诈皿,從而提高程序并發(fā)執(zhí)行的程度林束,以及提高資源利用率和系統(tǒng)的吞吐量
進(jìn)程和線程的區(qū)別和關(guān)系:
- 進(jìn)程是操作系統(tǒng)分配資源的最小單位,線程是程序執(zhí)行的最小單位稽亏。
- 一個進(jìn)程由一個或多個線程組成壶冒,線程是一個進(jìn)程中代碼的不同執(zhí)行路線;
- 進(jìn)程之間相互獨(dú)立截歉,但同一進(jìn)程下的各個線程之間共享程序的內(nèi)存空間(包括代碼段胖腾、數(shù)據(jù)集、堆等)及一些進(jìn)程級的資源(如打開文件和信號)瘪松。
- 調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多咸作。
以工廠模式比喻,江南皮革廠老板苦惱產(chǎn)量提不上去凉逛,于是給車間主任(進(jìn)程)開會性宏。
老板:你們幾個怎么搞得,怎么訂單(多任務(wù))多了状飞,產(chǎn)量還跟不上了毫胜?
車間主任A:手底下這么多人,哪管的過來诬辈。訂單不好分配酵使、進(jìn)度不無法跟蹤
車間主任B:A車間經(jīng)常跟我們車間搶物料(系統(tǒng)資源)
車間主任A:放***屁,你們的人上個月還搶我設(shè)備呢(系統(tǒng)資源)
老板:當(dāng)然你們說的都是客觀存在的因素焙糟,你們不會從手下挑選幾個有能力的人成立班組(線程)么口渔?權(quán)利下放(獨(dú)立調(diào)度)
車間主任A,B:好好好,試試穿撮。
linux下查看進(jìn)程的常用命令
- ps
- top
Javascript 單線程
在ecma-262中并無與線程相關(guān)的內(nèi)容缺脉,其單線程/多線程主要依賴于其JavaScript引擎(解釋器)痪欲。
瀏覽器的進(jìn)程和線程
以Chrome為例,Chrome瀏覽器使用多個進(jìn)程來隔離不同的網(wǎng)頁攻礼。因此在Chrome中打開一個網(wǎng)頁相當(dāng)于起了一個進(jìn)程
多進(jìn)程的原因:
- 因為進(jìn)程之間相互獨(dú)立业踢,一個瀏覽器選項卡無響應(yīng)不會影響其他的選項卡。
- 同樣因為進(jìn)程之間相互獨(dú)立礁扮,一個選項卡中如果惡意腳本知举,不會影響其他選項卡。例如太伊,Chrome 瀏覽器可以對處理用戶輸入(如渲染器)的進(jìn)程雇锡,限制其文件訪問的權(quán)限。
GUI線程:渲染布局(HTML,CSS等)
JS引擎線程:1.解析僚焦、執(zhí)行JS 2.與GUI線程互斥 (因為引擎是單線程的锰提,所以Javascript是單線程的)
定時觸發(fā)器線程:setTimeout/setInterval
事件觸發(fā)線程:將滿足觸發(fā)條件的時間放入任務(wù)隊列
異步HTTP請求線程:XHR所在線程
單線程的原因(引用自瀏覽器進(jìn)程?線程芳悲?傻傻分不清楚欲账!)
這是因為Javascript這門腳本語言誕生的使命所致:JavaScript為處理頁面中用戶的交互,以及操作DOM樹芭概、CSS樣式樹來給用戶呈現(xiàn)一份動態(tài)而豐富的交互體驗和服務(wù)器邏輯的交互處理。如果JavaScript是多線程的方式來操作這些UI DOM惩嘉,則可能出現(xiàn)UI操作的沖突罢洲; 如果Javascript是多線程的話,在多線程的交互下文黎,處于UI中的DOM節(jié)點就可能成為一個臨界資源惹苗,假設(shè)存在兩個線程同時操作一個DOM,一個負(fù)責(zé)修改一個負(fù)責(zé)刪除耸峭,那么這個時候就需要瀏覽器來裁決如何生效哪個線程的執(zhí)行結(jié)果桩蓉。當(dāng)然我們可以通過鎖來解決上面的問題。但為了避免因為引入了鎖而帶來更大的復(fù)雜性劳闹,Javascript在最初就選擇了單線程執(zhí)行院究。
Node.js中的進(jìn)程和線程
當(dāng)一個 Node.js 的應(yīng)用啟動的同時,它會啟動如下模塊:
- 一個進(jìn)程
- 一個線程:單線程意味著在當(dāng)前進(jìn)程中同一時刻只有一個指令在執(zhí)行本涕。
- 事件循環(huán)機(jī)制:盡管 JavaScript 是單線程的业汰,但通過使用回調(diào),promises, async/await 等語法菩颖,基于事件循環(huán)將對操作系統(tǒng)的操作異步化样漆,使得 Node 擁有異步非阻塞 IO 的特性。
- JS 引擎實例
- Node.js 實例
Node 運(yùn)行在單線程上晦闰,并且在事件循環(huán)中同一時刻只有一個進(jìn)程的任務(wù)被執(zhí)行放祟,每次同一時刻只會執(zhí)行一段代碼(多段代碼不會同時執(zhí)行)鳍怨。
只有一個js引擎在主線程上運(yùn)行。其他異步IO和事件驅(qū)動相關(guān)的線程通過libuv來實現(xiàn)內(nèi)部的線程池和線程調(diào)度
為什么Node.js也是單線程呢跪妥?
因為JavaScript起初是運(yùn)行在瀏覽器端的...你懂的鞋喇。
Node.js如何實現(xiàn)并行:
它通過事件輪詢(event loop)來實現(xiàn)并行操作,因此我們要避免阻塞操作骗奖,
Node.js可以多進(jìn)程/多線程么确徙?
利用
child_process
模塊 fork子進(jìn)程,實現(xiàn)多進(jìn)程利用
cluster
模塊fork子進(jìn)程--Master-Worker执桌,實現(xiàn)多進(jìn)程
- 使用第三方包node-threads-a-gogo 實現(xiàn)多線程
前端的異步場景
- 定時器
- 網(wǎng)絡(luò)請求
- 事件綁定
- ES6 Promise
定時器
再回到我們的代碼
console.log('sync...',1);
setTimeout(()=>{
console.log('sync...',2)
},2000);
console.log('sync...',3)
流程分析:
- 主程序調(diào)用棧
- 調(diào)用webAPI-
setTimeout
- 定時器線程計數(shù)2s
- 事件觸發(fā)線程將定時器時間放入任務(wù)隊列
- 主線程通過Event Loop遍歷任務(wù)隊列執(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');
注意:
- 定時任務(wù)可能不會按時執(zhí)行,延遲原因(等待同步任務(wù)鄙皇、等待CPU加載)
- 定時器嵌套5次之后最小間隔不能低于4ms
應(yīng)用場景:
- 防抖
- 節(jié)流
- 倒計時
- 動畫(存在丟幀)
案例分析
for(var i=1;i<=10;i++){
setTimeout(function() {
console.log(i)
},1000*i)
}
在這個案例中,我們期望每隔1s,依次打印i的值仰挣。但實際結(jié)果卻是每隔1s重復(fù)打印11伴逸。
問題的原因在于:
- 定時器需要等待同步任務(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 時我們在談?wù)撌裁?上)