請問如果一個線程能夠非同步執行,是否是因為另一個線程幫其承擔了同步的操作?

假設線程A要非同步的讀取文件:

Thread A:

read a file; // async

do something else;

在線程A非同步讀文件的時候,實現細節是否為又新建了一個線程B,B忙輪詢的讀取文件?

Thread B:

read a file; // sync, 等待磁碟

如果是這個原理,Thread A做事情邏輯上是比較通順了。

不過工作量始終是read_file, do_something兩件事情,如果是同步方式,就是A一個線程來做;如果是非同步方式,就由A, B兩個線程來做。後者增加了線程切換的開銷,是否做這兩件事情的效率反而降低了呢?


所謂非同步,那工作肯定不是在一個線程上完成的。

我們為什麼要非同步?因為想節省等待的時間。

先有等待,後有非同步。

不需要等待的時候,只有同步——哪怕CPU執行的順序和你寫的順序不一樣。

你把計算機想像成一個傳送帶就好了。

當你發現傳送帶上的一個貨物需要額外處理才能繼續傳送的時候,

你可以:

  • 暫停傳送,就地處理這個貨物,
  • 先傳送後面的,把這個貨物交給一個專門處理貨物的工人去處理;等這個貨物處理完了你在傳送它。

一般來說,非同步主要來處理IO和網路訪問。文件讀寫也好,收發請求也好,都不是你做的。你只是給操作系統發了個通知,操作系統在它創建的進程里完成了這一切,再把結果返回給你。

從這個意義上講,幾乎可以肯定非同步一定是涉及到多個線程/進程合作的。

----------------------------------------

但是呢,你負責的那一部分。是可以單線程,也可以多線程的,取決於你。

你可以弄個棧,把非同步調用的回調函數全push進去,然後定期檢查每個函數綁定的事件有沒有完成。完成了就把這個函數放到一個合適的tick執行。

也可以像Nodejs那樣,專門起個進程來處理event和回調。

你甚至可以起個進程當分發器,給回調函數來個負載均衡,分發給一大票進程分散式處理。


你的理解是正確的,很多人喜歡把「同步/非同步」跟「阻塞/非阻塞」聯繫一起,實際上他們沒有太大的關係。

同步/非同步是一個相對的概念,他會因為你的參照物不同而不同,說通俗點就是假如A既是調用者又是執行者,那麼就說這個調用是同步的,假如A調用了一個函數(廣義的),這個函數真正的執行者是B,那麼這個調用對於A來說就是非同步的。判斷非同步的要點就是是否通知「別人」幫你做事了。很容易理解吧。

所以同步非同步跟阻塞非阻塞沒有絕對的關係,不是說非同步就一定是非阻塞,也不是同步就一定是阻塞。正如你舉得這個例子,假如A中通知B來讀文件,此時如果A不斷輪詢B是否完成讀作業,那這個讀文件相對於A來說就是非同步阻塞的。假如A通知B來讀文件,然後不管B讀沒讀完直接往下走,最後B讀完之後通知A。這就是非同步非阻塞。所以判斷阻塞非阻塞在於是否等待函數的作業完成。

#--------------------------------------------------

線程是實現非同步的最常用手段之一(當然還包括多進程,協程)。比如瀏覽器對js非同步請求就是通過好幾個常駐線程來完成的,真實的請求並沒有發生在主線程當中。如果你站在主線程的角度(類似題目中參照物為A線程)來看,它調用的ajax請求就是非同步的。

你可以參考一下我最近寫的非同步RPC框架,正是通過線程實現的。https://github.com/nikoloss/pyfadeaway

並提供阻塞非阻塞兩個版本,希望它可以讓評論中的某人消停一下。另外多句嘴,朱涵俊的回答乍看上去好像是對的,其實根本上講就是在誤人子弟。對同步非同步根本沒有清晰的認識。一個相對的設計方法被搞成了絕對的定理公式。


非同步執行可以用在的地方為該部分任務的完成主體和該線程的完成主體不一樣,什麼意思呢,我舉兩個經常用非同步的例子:

  1. 題主說到的讀取文件:讀取文件的實質是將在硬碟上的數據複製到內存里,而這一操作的執行者並不是cpu,而是磁碟控制器。
  2. 發送http請求:當線程執行發送請求之後,就是網路里的傳輸問題了,並不需要cpu的操作。

所以並不存在兩個線程,而是本身該任務的完成就不是該線程來做的

所謂的同步,不過就是不做事在這裡等該任務處理完成

更形象點的說法:

假設cpu是一個人,I/O控制器也是一個人,所謂的線程的調度等同於一個人處理多個任務,而非同步/同步調用是一個人(cpu)命令另一個人(I/O控制器)去做一件事。

在非同步的情況下:我(cpu)並不等那個人(I/O控制器)完成任務才處理後面的事情,而是一分配完任務就干自己的其他事情了;

在同步的情況下:我(cpu)就在這裡等到那個人(I/O控制器)處理完我分配的任務,然後才幹後面的事情。


首先使用非同步可能是因為do something else比較緊急,為了先執行something,多點切換開銷不算什麼。然後,線程B在讀完前有相當一段時間在等硬碟讀數據,這裡是阻塞而不是忙等,cpu是空閑的,這段時間可以切換到線程A,這裡就節省時間了。最後你還要考慮多cpu的情況。。


根據1:彼此本不必相互等待的獨立無關請求數目

根據2:硬體真正能夠同時做事的核心/通信通道數

根據這些綜合起來就得到了非同步io事件驅動的最優架構。比如nginx和node.js。


掃盲:

1.同步或非同步是設計方法

2.單線程或多線程是實現(設計的)方法

3.同步可以用多線程去實現,非同步也可以用單線程去模擬

所以題主你不應該把設計方法與實現方法混為一談。


非同步同步是在於操作系統調用api上,操作系統api分非同步同步。

比如讀取文件,同步調用操作系統會把當前線程掛起,完成之後再恢復。掛起的時候,操作系統就切換到其他線程了,這個新線程可能是另外的線程,也可能是其他進程的線程。

非同步調用則不同,調用之後操作系統立即返回,並不阻塞,這個時候線程還能去做其他事情,如果文件讀取完成了,操作系統會通知成功,然後再去處理。

為啥最近都流行非同步,主要是非同步效率高。同步調用需要創建太多的線程,而且調度不可控,線程調度開銷也很大。而非同步是沒有線程切換的,內部維護一個隊列,哪個io完成了就去操作哪個。

對於業務代碼來說,同步代碼容易寫,非同步一般採用回調,有點反人類思維。有的語言有yield,可以把非同步寫成同步差不多的代碼。

而對於類似web伺服器來說,非同步模式更符合實際,客戶端發起http請求,伺服器處理請求,事件驅動,業務代碼就跟一個插件一樣。需要注意的是,在非同步模式中,業務代碼盡量避免同步調用。否則系統性能會嚴重下降。

同樣是高級語言,nginx+lua很快,nginx+php就很慢,其中一個因素是php沒法實現非同步。而lua跟nginx是絕配,高並發首選。

回到題目,非同步沒有2個線程,只有一個線程。一般為了達到性能頂峰,會啟動多個進程,因為cpu是多核的,幾個核就啟動幾個。超過之後越多效率越低。

題主應該關心的是文件完成之後線程如何知道,對於一般的來說,就是輪循,等多少時間輪循一次,看起來效率反而低了。那是你這個線程除了等文件讀取完成之外沒事做。但nginx這樣的程序不會沒事做,在等一個請求完成文件讀取過程中,他可以處理其他請求,等這個文件處理完成了,再來處理原來的請求。但是業務代碼要讀取一個文件該怎麼辦呢,nginx並不知道你要讀取說文件,也不能知道什麼時候完成,就會打破nginx非同步模型,導致性能急劇下降。一個方法就是讀取文件也交給nginx,把讀文件變成一個nginx子請求,這樣nginx就能統一管理非同步隊列了。


推薦閱讀:

理解 React,但不理解 Redux,該如何通俗易懂的理解 Redux?
一個 ul 里有若干 li,點擊 li 時能方便地知道這是 ul 中的第幾個 li 嗎?
相比Angular,Avalon有什麼缺點呢?
瀏覽器允許的並發請求資源數是什麼意思?
有哪些利於前端新手練習、理解JS的獨立小項目?

TAG:JavaScript | Java | 非同步 | 同步 | 多線程 |