首頁 > web前端 > js教程 > 主體

javascript中異步單線程的解析(圖文)

不言
發布: 2018-09-10 16:55:42
原創
1368 人瀏覽過

這篇文章帶給大家的內容是關於JS中非同步單線程的解析(圖文),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

對於通常的developer(特別是那些具備平行運算/多執行緒背景知識的developer)來講,js的非同步處理著實稱得上詭異。而這個詭異從結果上講,是由js的「單線程」這個特性所導致的。

我曾試著用「先定義後展開」的教科書方式去講解這一塊的內容,但發現極其痛苦。因為要理清楚這個東西背後的細節,並將其泛化、以更高的視角來看問題,著實涉及非常多的基礎知識。等到我把這些知識講清楚、講完,無異於逼迫讀者抱著操作系統、電腦網路這樣的催眠書看上好個幾章節,著實沉悶而乏味。

更關鍵的是,在走到那一步的時候,讀者的精力早已消耗殆盡,完全沒有心力再去關心這個最開始的問題——js的異步處理為何詭異。

所以,我決定反過來,讓我們像一個初學者一樣,從一無所知開始,

#先使用「錯誤的理念」去開始我們的討論,然後用程式碼去發現和理念相違背的地方。

再做出一些修正,再考察一些例子,想想是否還有不太滿意和清楚的地方,再調整。如此往復,我們會像偵探一樣,先從一個不大正確​​的假設開始,不斷尋找證據,不斷修正假設,一步步追尋下去,直到抵達最後完整的真相。

我想,這樣的寫作方式,更符合一個人真正的求知和研究過程,並且能夠為你帶來更多關於「探索問題」的啟發。我想,這樣的思考方式和研究理念,比普通的知識更為重要。它能夠讓你成為知識的獵人,有能力獨立覓食,而不必被迫成為嬰孩,只能坐等他人餵食。

好了,讓我們先從一塊js程式碼,開始我們的探索之旅。

console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 5000);

console.log('No. 2');
登入後複製

輸出結果是:

No. 1
No. 2
setTimeout callback
登入後複製

這塊程式碼幾乎沒什麼複雜的東西,全是列印語句。唯一的特別是函數setTimeout,根據粗略的網路資料顯示,它接受兩個參數:

  • 第一個參數是callback函數,就是讓它執行完之後,回過頭來呼叫的函數。

  • 另一個是時間參數,用於指定多少微妙之後,執行callback函數。這裡我們使用了5000微妙,也即是5秒鐘。

另一個重點是,setTimeout是一個非同步函數,意思是我的主程式不必去等待setTimeout執行完畢,將它的運作過程丟到別的地方執行,然後主程式繼續往下走。也即是,主程式是一個步調、setTimeout是另一個步調,也就是「非同步」的方式跑程式碼。

如果你有一些平行運算或多執行緒程式設計的背景知識,那麼上面的語句就再熟悉不過了。如果在多執行緒環境,無非是另一個執行緒去執行列印語句console.log('setTimeout callback')。然後主線程繼續往下走,新線程去負責列印語句,清晰明了。

所以綜合起來,這段程式碼的意思是,主執行緒執行到語句setTimeout時,就把它交給“其它地方”,讓這個“其它地方”等待5秒鐘之後運行。而主線程繼續往下走,去執行「No. 2」的列印。所以,由於其它部分要等待5秒鐘之後才運行,而主線程立刻往下運行了“No. 2”的打印,最終的輸出結果才會是先打印“No. 2”,再打印“setTimeout callback 」。

嗯,so far so good。一切看來都比較美好。

如果我們對上述程式做一點變動呢?例如,我可不可以讓「setTimeout callback」這個資訊先被印出來呢?因為在平行計算中,我們常遇到的問題便是,由於你不知道多個執行緒之間誰執行得快、誰執行得慢,所以我們無法判定最終的語句執行順序。這裡我們讓「setTimeout callback」停留了5秒鐘,時間太長了,要不短一點?

console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 1);

console.log('No. 2');
登入後複製

我們將傳遞給setTimeout的參數改成了1毫秒。多次運行後會發現,結果竟然沒有改變? !似乎有點反常,要不再改小一點?改成0?

console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 0);

console.log('No. 2');
登入後複製
登入後複製

多次運行後,發現依舊無法改變。這其實是有點奇怪了。因為通常的平行計算、多執行緒程式設計中,透過多次運行,你其實是可以看到各種無法預期的結果的。在這裡,竟然神奇地得到了相同的執行順序結果。這就反常了。

但我们还无法完全下一个肯定的结论,可不可能因为是setTimeout的启动时间太长,而导致“No. 2”这条语句先被执行呢?为了做进一步的验证,我们可以在“No. 2”这条打印语句之前,加上一个for循环,给setTimeout充分的时间去启动。

console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 0);

for (let i = 0; i <p>运行这段代码,我们发现,"No. 1"这条打印语句很快地显示到了浏览器命令行,等了一秒钟左右,接着输出了</p><pre class="brush:php;toolbar:false">No. 2
setTimeout callback
登入後複製

诶?!这不就更加奇怪了吗?!setTimeout不是等待0秒钟后立刻运行吗,就算启动再慢,也不至于等待一秒钟之后,还是无法正常显示吧?况且,在加入这个for循环之前,“setTimeout callback”这条输出不是立刻就显示了吗?

综合这些现象,我们有理由怀疑,似乎“setTimeout callback”一定是在“No. 2”后显示的,也即是:setTimeout的callback函数,一定是在console.log('No. 2')之后执行的。为了验证它,我们可以做一个危险一点的测试,将这个for循环,更改为无限while循环。

console.log('No. 1');

setTimeout(function(){
    console.log('setTimeout callback');
}, 0);

while {}  // dangerouse testing

console.log('No. 2');
登入後複製

如果setTimeout的callback函数是按照自己的步调做的运行,那么它就有可能在某个时刻打印出“setTimeout callback”。而如果真的是按照我们猜测的那样,“setTimeout callback”必须排在“No. 2”之后,那么浏览器命令行就永远不会出现“setTimeout callback”。

运行后发现,在浏览器近乎要临近崩溃、达到内存溢出的情形下,“setTimeout callback”依旧没有打印出来。这也就证明了我们的猜测!

这里,我们第一次出现了理念和现实的矛盾。按照通常并行计算的理念,被扔到“其它地方”的setTimeout callback函数,应该被同时运行。可事实却是,这个“其它地方”并没有和后一条打印“No. 2”的语句共同执行。这时候,我们就必须要回到基础,回到js这门语言底层的实现方式上去追查,以此来挖掘清楚这后面的猫腻。

js的特性之一是“单线程”,也即是从头到尾,js都在同一根线程下运行。或许这是一个值得调查深入的点。想来,如果是多线程,那么setTimeout也就该按照我们原有的理念做执行了,但事实却不是。而这两者的不同,便在于单线程和多线程上。

找到了这个不同点,我们就可以更深入去思考一些细节。细想起来,所谓“异步”,就是要开辟某个“别的地方”,让“别的地方”和你的主运行路线一起运行。可是,如果现在是单线程,也就意味着计算资源有且只有一份,请问,你如何做到“同时运行”呢?

这就好比是,如果你去某个办事大厅,去缴纳水费、电费、天然气。那么,我们可以粗略地将它们分为水费柜台、电费柜台、天然气柜台。那么,如果我们依次地“先在水费柜台办理业务,等到水费的明细打印完毕、缴纳完费用后;再跑去电费柜台打印明细、加纳费用;再跑去天然气柜台打印明细、加纳费用”,这就是一个同步过程,必须等待上一个步骤做完后,才能做下一步。

而异步呢,就是说我们不必在某个环节浪费时间瞎等待。比如,我们可以在“打印水费明细”的空闲时间,跑到电费和天然气柜台去办理业务,将“电费明细、天然气明细的打印”这两个任务提前启动起来。再回过头去缴纳水费、缴纳电费、缴纳天然气费用。其实,这就是华罗庚推广优选法的时候举的例子,烧水、倒茶叶、泡茶,如何安排他们的顺序为高效。

显然,异步地去做任务更高效。但这要有一个前提,就是你做任务的资源,也即是干活的人或者机器,得有多份才行。同样按照上面的例子来展开讨论,虽然有水费、电费、天然气这三个柜台,可如果这三个柜台背后的办事人员其实只有一个呢?比如你启动了办理水费的业务,然后想要在办理水费业务的等待期,去电费柜台办理电费业务。表面上,你去电费柜台下了申请单,请求办理电费业务,可却发现根本没有办事员去接收你的这个业务!为何?因为这有且只有一个的办事员,还正在办理你的水费业务啊!这时候,你的这个所谓的“异步”,有何意义?!

所以从这个角度来看,当计算资源只有一份的时候,你做“异步”其实是没什么意义的。因为干活的资源只有一份,就算在表面做了名义上的“异步”,可最终就像上面的多柜台单一办事员那样,到了执行任务层面,还是会一个接一个地完成任务,这就没有意义了。

那麼,js的特性是」單執行緒「 」非同步「,不就正是我們討論的「沒有意義」的情況嗎? !那為何要多次一舉,做一些沒有意義的事情呢?

嗯......事情變得越來越有趣了。

通常來講,如果一個事件出現了神奇和怪異的地方,基本上都是因為我們忽略了某個細節,或者對某個細節有誤解或是錯誤理解。要想把問題解決,我們就必須不斷回顧已有材料,在不斷重複檢驗中,發現那幾根我們忽略的貓膩。

讓我們回顧一下關於js非同步的宣傳影片。通常為了說明js異步的必要性,會舉出瀏覽器的資源載入和頁面渲染這個矛盾。

渲染,可以比較粗糙地理解為將「畫面」畫出來的過程。例如,瀏覽器要將頁面上的按鈕、圖片顯示出來,就必須有一個將「圖片」在網頁上畫出來的動作。又或是,作業系統要將「桌面」這個圖形介面顯示在顯示器上,就必須要把它對應的這個「畫面」在顯示器上畫出來的動作。歸結起來,這個「畫出來」的過程,就被稱之為「渲染」。

例如,你點擊頁面上的一個button,讓瀏覽器去後端資料庫將資料報表取出來,在網頁上把數字顯示出來。而如果js不支援異步的話,整個網頁的就會停留,也即是“卡”,在滑鼠點擊按鈕這一動作上,頁面無法完成後續的渲染工作。一直要等到後端把資料回到了前端,程式流程才能夠繼續跑下去。

所以這裡,js的“非同步”其實是為了讓瀏覽器將“載入”這個任務分給“其它地方”,讓“載入過程”和“渲染過程”同步進行下去。

等等,又是這個「其它地方」? ! !

我擦,不是說js是單線程而麼,計算資源不是只有一份麼,怎麼又可以「一邊載入、一邊渲染」了? ! WTF,你這是在逗我玩兒麼? !

艹,這裡面哪一句話是真的? !到底js是單線是真的?還是說瀏覽器可以同時做「一邊載入、一邊渲染」這個事情是真的? !

如何才能解決這個疑惑? !很顯然,我們必須要深入瀏覽器的內部,去看它到底是怎麼樣被設計的。

在搜尋引擎中,做一些關於瀏覽器和js的搜索,我們不難得到一些基本資訊。 js並不是瀏覽器的全部,瀏覽器要掌管的事情太多了,掌管js的只是瀏覽器的一個元件,叫做js引擎。而最著名的、並在Chrome中使用的,就是大名鼎鼎的V8引擎,它負責js的解析與運作。

另一方面我們也知道,使用js的一個很大原因,是因為它能夠自由地去操控DOM元素、去執行Ajax異步請求、能夠像我們最開始舉的例子那樣,使用setTimeout做非同步任務分配。這些都是js優秀特性。

可令人驚訝的事情來了,當我們去探索這個掌管js一切的V8引擎的時候,我們卻發現,它並不提供DOM的操控、Ajax的執行以及setTimeout的特性:

javascript中異步單線程的解析(圖文)

上圖來自Alexander Zlatkov,它的結構是:

  1. ##JS引擎

  • Memory Heap

  • #Call Stack

  • Web APIs

    • DOM (Document)

    • #Ajax (XMLHttpRequest)

    • Timeout (setTimeout)

  • Callback Queue

  • #Event Loop

  • 明明是js的特性,為什麼這些職能卻不是由js的引擎來掌管呢?嗯,interesting~~~

    誒!不是「單線程」麼,不是載入過程被丟到其它地方麼? ! js是單線程,也也就是js在js引擎中是單線程、只能夠分到一份運算資源,可是,載入資料的Ajax這個feature不是沒有被放到js引擎麼? !

    艹!真TM是老狐狸!還以為「單線程」和「一邊載入、一邊渲染」這兩種說法只有一種是對的,但結果是,都對!為什麼呢?因為只說了js是單線程,所以沒說瀏覽器本身是單線程!所以咯,渲染相關的js部分可以和資料載入的Ajax部分是可以同時進行的,因為它們根本就在兩個模組,也就是兩個執行緒嘛!所以當然可以並行啊! WTF!

    诶~等等,让我们再仔细看看上面这张图呢?!Ajax不在js引擎里,可是setTimeout也不在js引擎里面啊!!如果Web APIs这部分是在不同于js引擎的另外一根线程里,它们不就可以实现真正意义上的并行吗?!那为何我们开头的打印信息“setTimeout callback”,无法按照并行的方式,优先于“No. 2”打印出来呢?

    嗯......真是interesting......事情果然没有那么简单。

    显然,我们需要考察更多的细节,特别是,每一条语句在上图中,是按照什么顺序被移动、被执行的。

    谈到语句的执行顺序,我们需要再一次将关注点放回到js引擎上。再次回看上面这幅结构图,JS引擎包含了两部分:一个是 memory heap,另一个是call stack。前者关于内存分配,我们可以暂时放下。后面即是函数栈,嗯,它就是要进一步理解执行顺序的东西。

    函数栈(call stack)为什么要叫做“栈(stack)”呢?为什么不是叫做函数队列或者别的神马?这其实可以从函数的执行顺序上做一个推断。

    函数最开始被引进,其实就是为了代码复用和模块化。我们期望一段本该出现的代码,被单独提出来,然后只需要用一个函数调用,就可以将这段代码的执行内容给插入进来。

    所以,如果当我们执行一段代码时,如果遇到了函数调用,我们会期望先去将函数里面的内容执行了,再跳出来回到主程序流,继续往下执行。

    所以,如果把一个函数看作函数节点的话,整个执行流程其实是关于函数节点的“深度优先”遍历,也即是从主函数开始运行的函数调用,整个呈深度优先遍历的方式做调用。而结合算法和数据结构的知识,我们知道,要实现“深度遍历”,要么使用递归、要么使用stack这种数据结构。而后者,无疑更为经济使用。

    所以咯,既然期望函数调用呈深度优先遍历,而深度优先遍历又需要stack这种数据结构做支持,所以维护这个函数调用的结构就当然呈现为stack的形式。所以叫做函数栈(stack)。

    当然,如果再发散思考一下,操作系统的底层涌来维护函数调用的部分也叫做函数栈。那为何不用递归的方式来实现维护呢?其实很简单,计算机这么个啥都不懂的东西,如何知道递归和返回?它只不过会一往无前的一直执行命令而已。所以,在没有任何辅助结构的情况下,能够一往无前地执行的方式,只能是stack,而不是更为复杂的递归概念的实现。

    另一方面,回到我们最开头的问题,矛盾其实是出现在setTimeout的callback函数上。而上面的结构图里,还有一部分叫做“callback queue”。显然,这一部分也是我们需要了解的东西。

    结合js的call stack和callback queue这两个关键词,我们不难搜索到一些资料,来展开讨论这两部分是如何同具体的语句执行相结合的。

    先在整体上论述一下这个过程:

    • 正常的语句执行,会一条接一条地压入call stack,执行,再根据执行的内容继续压入stack。

    • 而如果遇到有Web APIs相关的语句,则会将相应的执行内容扔到Web APIs那边。

    • Web APIs这边,可以独立于js引擎,并行地分配给它的语句,如Ajax数据加载、setTimeout的内容。

    • Web APIs这边的callback function,会在在执行完相关语句后,被扔进“callback queue”。

    • Event loop会不断地监测“call stack”和“callback queue”。当“call stack”为空的时候,event loop会将“callback queue”里的语句压入到stack中,继续做执行。

    • 如此循环往复。

    以上内容比较抽象,让我们用一个具体的例子来说明。这个例子同样来自于Alexander Zlatkov。使用它的原因很简单,因为Zlatkov在blog中使用的说明图,实在是相当清晰明了。而目前我没有多余的时间去使用PS绘制相应的结构图,就直接拿来当作例子说明了。

    让我们考察下面的代码片段:

    console.log('Hi');
    setTimeout(function cb1() { 
        console.log('cb1');
    }, 5000);
    console.log('Bye');
    登入後複製

    哈哈,其实和我们使用的代码差不多,只是打印的内容不同。此时,在运行之前,整个底层的结构是这样的:

    javascript中異步單線程的解析(圖文)

    然后,让我们执行第一条语句console.log('Hi'),也即是将它压入到call stack中:

    javascript中異步單線程的解析(圖文)

    然後js引擎執行stack中最上層的這句話。對應的,瀏覽器的控制台就會列印出訊息「Hi」:

    javascript中異步單線程的解析(圖文)

    #由於這條語句被執行了,所以它也從stack中消失:

    javascript中異步單線程的解析(圖文)

    再來壓入第二條語句setTimeout

    javascript中異步單線程的解析(圖文)

    執行setTimeout(function cb1() { console.log('cb1'); }, 5000);

    javascript中異步單線程的解析(圖文)

    #注意,由於setTimout部分並沒有被包含在js引擎中,所以它就直接被丟給了Web APIs的Timeout部分。這裡,stack中的藍色部分起到的作用,就是將對應的內容「timer、等候時間5秒、回呼函數cb1」丟給Web APIs。然後這條語句就可以從stack中消失了:

    javascript中異步單線程的解析(圖文)

    #繼續壓入下一語句console.log('Bye')

    javascript中異步單線程的解析(圖文)

    注意,此時在Web APIs的部分,正在並行於js引擎執行對應的語句,即:等候5秒鐘。 Okay,timer繼續它的等待,而stack這邊已經有語句了,所以需要把它執行掉:

    javascript中異步單線程的解析(圖文)

    對應的瀏覽器控制台,就會顯示出「Bye」的訊息。而stack中運行後的語句,就該消失:

    javascript中異步單線程的解析(圖文)

    此時,stack已經為空。 Event loop偵測到stack為空,自然就想要將callback queue中的語句壓入到stack中。但此時,callback queue中也為空,於是Event loop只好繼續循環檢測。

    另一方面,Web APIs這邊的timer,並行地在5秒鐘後開始了它的執行-什麼也不做。然後,將它對應的回呼函數cb1(),放到callback queue中:

    javascript中異步單線程的解析(圖文)

    ##Event loop由於一直在循環檢測,此時,看到callback queue有了東西,就迅速將它從callback queue中取出,然後將其壓入到stack裡:

    javascript中異步單線程的解析(圖文)

    現在Stack裡有了東西,就需要執行回呼函數

    cb1()。而cb1()裡面呼叫了    console.log('cb1')這語句,所以需要將它壓入stack中:

    javascript中異步單線程的解析(圖文)

    #stack繼續執行,現在它的最上層是

       console.log('cb1'),所以需要先執行它。於是瀏覽器的控制它印出對應的訊息「cb1」:

    javascript中異步單線程的解析(圖文)

    將執行了的

       console.log('cb1' )語句彈出堆疊:

    javascript中異步單線程的解析(圖文)

    繼續執行

    cb1()剩下的語句。此時,cb1()已經沒有其它需要執行的語句了,也即是它被運行完畢,所以,將它也從stack中彈出:

    javascript中異步單線程的解析(圖文)

    整个过程结束!如果从头到尾看一遍的话,就是下面这个gif图了:

    javascript中異步單線程的解析(圖文)

    相当清晰直观,对吧!

    如果你想进一步地把玩js的语句和call stack、callback queue的关系,推荐Philip Roberts的一个GitHub的开源项目:Loupe,里面有他online版本供你做多种尝试。

    有了这些知识,现在我们回过头去看开头的那段让人产生疑惑的代码:

    console.log('No. 1');
    
    setTimeout(function(){
        console.log('setTimeout callback');
    }, 0);
    
    console.log('No. 2');
    登入後複製
    登入後複製

    按照上面的js处理语句的顺序,第一条语句console.log('No. 1')会被压入stack中,然后被执行的是setTimout

    根据我们上面的知识,它会被立刻扔进Web APIs中。可是,由于这个时候我们给它的等待时间是0,所以,它的callback函数console.log('setTimeout callback')会立刻被扔进“Callback Queue”里面。所以,那个传说中的“其它地方”指的就是callback queue。

    那么,我们能够期望这一条console.log('setTimeout callback')先于“No. 2”被打印出来吗?

    其实是不可能的!为什么?因为要让它被执行,首先它需要被压入到call stack中。可是,此时call stack还没有将程序的主分支上的语句执行完毕,即还有console.log('No. 2')这条语句。所以,event loop在stack还未为空的情况下,是不可能把callback queue的语句压入stack的。所以,最后一条“setTimeout callback”的信息,一定是会排在“No. 2”这条信息后面被打印出来的!

    这完全符合我们之前加入无限while循环的结果。因为主分支一直被while循环占有,所以stack就一直不为空,进而,callback queue里的打印“setTimeout callback”的语句就更不可能被压入stack中被执行。

    探索到这里,似乎该解决的问题也都解决了,好像就可以万事大吉,直接封笔走人了。可事实却是,这才是我们真正的泛化讨论的开始!

    做研究和探索,如果停留于此,就无异于小时候自己交作业给老师,目的仅仅是完成老师布置的任务。在这里,这个老师布置的任务就是文章开头所提出的让人疑惑的代码。可是,解决这段代码并不是我们的终极目的。我们需要泛化我们的所学和所知,从更深层次的角度去探索,为什么我们会疑惑,为什么一开始无法发现这些潜藏在表面之下不同。我们要继续去挖掘,我们到底在哪些最根本的问题上出现了误解和错误认识,从而导致我们一路如此辛苦,无法在开头看到事情的真相。

    回顾我们的历程,一开始让我们载跟斗的,其实就是对“异步”和“多线程”的固定假设。多线程了,就是异步,而异步了,一定是多线程吗?我们潜意识里是很想做肯定回答的。这是因为如果异步了,但却是单线程,整个异步就没有意义了(回忆那个多柜台、单一办事员的例子)。可js却巧妙地运用了:使用异步单线程去分配任务,而让真正做数据加载的Ajax、或者时间等待的setTimeout的工作,扔给浏览器的其它线程去做。所以,本质上js虽然是单线程的,可在做实际工作的时候,却利用了浏览器自身的多线程。这就好比是,虽然是多柜台、单一办事员,可办事员将缴纳电费、水费的任务,外包给其它公司去做,这样,虽然自己仍然是一个办事员,但却由于有了外包服务的支持,依旧可以一起并行来做。

    另一方面,js的异步、单线程的特性,逼迫我们去把并行计算中的“同步/异步、阻塞/非阻塞”等概念理得更清楚。

    “同步”的英文是synchronize,但在中文的语境下,却很容易和“同时”挂钩。于是,在潜意识里有可能会有这样一种联想,“同步”就是“同时”,所以,一个同步(synchronize)的任务就被理解为“可以一边做A,一边做B”。而这个潜意识的印象,其实完全是错误的(一般做A一边做B,其实是“异步”+“并行”的情况)。

    但在各类百科词典上,确实有用“同时”来作为对“同步”的解释。这是为什么呢?其实这是对”同步“用作”同时“的一个混淆理解。如果仔细考虑”同时“的意思,细分起来,其实是有两种理解:

    • 同一個時刻(at the same time),例如在9:00 a.m這個時間點,我們既在做A也在做B。

    • 另一個是同一個時間參考系,也就是所謂的clock on the wall是同一個。

    前者很容易理解,這裡我重點解釋一下後者。例如,我在中國大陸同美國的一個同學開微信語音聊天,我這邊是22:00,他那邊是9:00。我們做聊天這件事的時候,是同一時刻(at the same time),但卻不在同一個時間參考體系(clock on the wall)。而在計算機中討論的同步,其實討論的是後者的”同一參考系“,同步,就是讓我們的參考系統一起來,放到同一個體系之下。

    又比如,我們在生活中很容易說,同步你的電腦、同步你的手機通訊錄、同步你的相冊,說的是什麼呢?就是讓你的各個客戶端:PC、手機,同server端伺服器的內容都保持一致,也即是大家都被放到一個一致的參考系裡面。不要說,你在PC裡有照片A,而在手機裡沒有A卻有B,這個時候,談論PC裡信息人與談論手機裡信息的人,就是在雞同鴨講。究其原因,就是沒有把大家放到同一個參考系裡面。

    所以,同步synchronize所指的」同時「,是大家把牆上的時鐘都調整到一致、調整為同一個步調,也即是同時、同一時間參考系的意思。而不是說,讓事情在同一時刻並列發生。自然的,什麼是異步(asynchronize)呢,異步就是大家的時間參考係是不同的,例如我在中國大陸、你在美國,我們的時間參考體係是不同的,這就是異步,不在同一個步調、頻段上。

    事實上,每一個獨立的人、每一塊獨立的運算資源,它都代表了一個各自的參考體系。只要你將任務分發給了其他人或其它計算資源,此時,就出現了兩個參考體系:一個是原有主分支的參考體系,另一個是新的計算資源的參考體系。在平行計算中,有一個同步機制是使用語句barrier,目的是讓所有的計算分支在這一個位置節點都完成了計算。為什麼說它是一種同步機制?依照我們統一參考體系的理解,就是保證其他所有計算分支完成計算,也就保證了其它分支的消失,從而只剩下主分支這一個參考體系。於是大家可以談論同樣的東西,說同樣的話,不會有誤解。

    另一方面,如果要更深入地理解js的設計,我認為還需要回到電腦歷史的初期,例如那個只有單核心的分時系統的時代。在那樣一個時代,作業系統所受到的硬體上的限制,不亞於js引擎在瀏覽器中所受到的限制。在同樣的限制下,曾經的作業系統會如何去巧妙運用那極為有限的運算資源,讓整個作業系統給人以平滑、順暢和功能強大的錯覺?我想,js的這些設計必定和作業系統早期的設計緊密相關。所以在這個層面上,它將再一次回到作業系統這樣的基礎知識。能否吃透現代的技術,其實很大層面上取決於你是否吃透了設計的歷史,是否理解在那些資源枯竭的年代,各路大神是如何巧妙地逢山開路,遇水搭橋。無論現代的電腦硬體資源有多豐富,它必定會因為目標的主次關係、業務的主次關係而受到限制。而如何在限制中跳舞和創造,這是能夠貫穿整個歷史的共同性問題。

    相關推薦:

    詳解JavaScript非同步程式設計技術

    #

    以上是javascript中異步單線程的解析(圖文)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    相關標籤:
    來源:php.cn
    本網站聲明
    本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
    作者最新文章
    熱門教學
    更多>
    最新下載
    更多>
    網站特效
    網站源碼
    網站素材
    前端模板