互連網我來了 -- 2. js中"非同步/阻塞"等概念的簡析
一、什麼是”非同步非阻塞式”?
這個名字聽起來很噁心難懂,但如果以 買內褲 這件事情來比喻執行程式的話就很容易理解“非同步非阻塞式”的涵義了。
例如你是一個CPU的線程,你需要去執行一段 買內褲的程式, 你所需執行的步驟大致如下,
到一個商店裡問老闆, 你們店裡還有沒有nb牌內褲? 買到內褲,穿上去小賣店買點火腿回家喂狗
這時候,你作為一個線程,你可能會遇到幾種狀況或選擇。
店裡面沒貨了,老闆一直不答應你(阻塞你),你也一直等著(同步),第三天有貨了才告訴你有了,你趕緊拿到穿上,再去買火腿 – [
同步阻塞模式]老闆馬上告訴你“沒貨了”(不阻塞你),然後你每天去店裡問一聲,直到有貨了趕緊穿上,再去買東西喂狗 – [
同步非阻塞]你讓老闆有貨了打電話叫你過來取,老闆說“好的”(非阻塞),然後你就先去買東西喂狗了,過了兩天老闆叫你過去,你才拿到內褲並穿上 – [
非同步非阻塞]你讓老闆有貨了打電話叫你過來取, 老闆又沒答應你(阻塞), 然後你本來打算去先去買火腿的(非同步),這時候也沒法去了。 – [
非同步阻塞]
很明顯,第三種方法是最聰明,前兩種方法都略顯效率低下。所以根據我們的理解,
同步或非同步,表明著是否需要將整個流程順序地完成
阻塞或非阻塞,意味著你調用的函數會不會立刻告訴你結果
二、在js中的阻塞與同非同步
你有一個函數和一段程式。
2.1 js中的同步阻塞
// 這是一個阻塞式函數, 將一個檔案複製到另一個檔案上function copyBigFile(afile, bfile){ var result = copyFileSync(afile,bfile); return result;}
調用這個”copyBigFile()”函數,將一個大檔案複製到另一個檔案上,將耗時1小時。意味著這個函數的將在一個小時之後返回。
//這是一段程式console.log("start copying ... "); var a = copyBigFile('A.txt', 'B.txt'); //這行程式將耗時1小時console.log("Finished"); // 這行程式將在一小時後執行console.log("處理一下別的事情"); // 這行程式將在一小時後執行console.log("Hello World, 整個程式已載入完畢,請享用"); // 這行程式將在一小時後執行
以上的程式就是一個同步阻塞的例子,因為copyFileSync函數傳回值的過程需要漫長的時間,所以線程也無法繼續執行下去,只能等待。
2.2 js中的同步非阻塞
// 這是一個非阻塞式函數// 如果複製已完成,則返回 true, 如果未完成則返回 falsefunction copyBigFile(afile,bfile){ var copying = copyFileAsync(afile, bfile); var isFinished = !copying; return !isFinished; }
調用這個函數將立刻返回結果,然後你的程式就可以寫成
console.log("start copying ... "); while( a = copyBigFile('A.txt', 'B.txt')){ console.log("在這之間還可以處理別的事情");} ; console.log("Finished"); // 這行程式將在一小時後執行console.log("Hello World, 整個程式已載入完畢,請享用"); // 這行程式將在一小時後執行
一個非阻塞式的函數,給你的編程帶來了更多的便利,你可以在長IO操作的同時,寫點其他的程式,提高效率。執行結果如下
start copying ...在這之間還可以處理別的事情在這之間還可以處理別的事情在這之間還可以處理別的事情...FinishedHello World, 整個程式已載入完畢,請享用
2.3 js中的非同步非阻塞
我們看到,一個非阻塞式的函數能給我們編程帶來許多靈活性,我們喜歡非阻塞式的函數。
但是,又可以看到同步的程式需要在一個迴圈中輪詢結果,迴圈裡面的程式會被執行好多遍,所以並不好控制來寫一些正常的程式,很難再利用起來。
所以我們需要一種更為合理的方式對非阻塞式的函數進行利用。
也就是我不會主動地去詢問結果,而是當你有了結果的時候再來通知我。
// 這是一個非阻塞式函數
// 如果複製已完成,則返回 true, 如果未完成則返回 false
//非阻塞式的有非同步通知能力的函數//以下不需要看懂,只用知道這個函數會在完成copy操作之後,執行successfunction copyBigFile(afile,bfile, callback){ var copying = copyFileAsync(afile, bfile, function(){ callback();}); var isFinished = !copying; return !isFinished; }
這個函數不同於上一個同步非阻塞函數的地方在於,它具有通知功能,也就是說,它能夠在完成操作之後主動地通知程式,“我完成了”。於是有程式如下,
console.log("start copying ... "); copyBigFile("A.txt","B.txt", function(){ console.log("Finished"); //一個小時後被執行 console.log("Hello World, 整個程式已載入完畢,請享用"); //一個小時後被執行 })console.log("幹別的事情"); console.log("做一些別的處理");
程式在調用copyBigFile函數之後,可以立即獲得傳回值,線程沒有被阻塞住,於是還可以去幹些別的事情,然後當copyBigFile完成之後,會執行指定的函數。所以程式的輸出應為,
start copying ...幹別的事情做一些別的處理FinishedHello World, 整個程式已載入完畢,請享用
在這種情況下,程式更容易控制,流程更為清晰。一些“別的事情”可以在函數還未通知之前進行處理,充分地提高了線程的利用效率。
三、提升
在以上的程式中,我們的程式需要拷貝一個巨大的檔案。其實拷貝檔案這個過程是留給系統的IO調用進行完成的,而我們的線程並不需要去處理拷貝的細節。
所以通過非阻塞式的函數,我們能夠 有可能 利用線程資源去幹一下別的事情。
而通過非同步呼叫方式,能使得程式流程 更容易控制,更有效率地利用線程資源。
而js是通過傳遞函數實現非同步。(還有可以通過promise的方式來實現非同步…)