本文為翻譯作品,若您具備一定的英語閱讀水平,建議閱讀原文。

此文所需閱讀時間,預計8分鐘。

Asynchronous programming. Await the Future?

luminousmen.com
圖標

這是關於非同步編程的系列文章的第三篇文章。整個系列嘗試回答一個簡單的問題:「什麼是非同步?」。

當我剛開始深入研究這個問題時,年輕的我以為我知道它是什麼。事實證明,年輕的我並無法完整的陳述何為非同步編程。時過境遷,現在讓我們一起討論一下!

整個系列:

黃華智:何為「非同步編程」?系列一:阻塞操作與非阻塞操作?

zhuanlan.zhihu.com
圖標
黃華智:何為「非同步編程」?系列二:合作型多任務處理?

zhuanlan.zhihu.com
圖標

黃華智:何為「非同步編程「?系列三:等待未來?

zhuanlan.zhihu.com
圖標
  • 非同步編程:Python3.5+

一些應用程序實現並行,採用多進程來代替多線程(第一篇)。在這篇文章中,我將就線程實現來展開,但你可以很輕鬆地將其切換到進程。因為儘管一些程序細節存在不同,但本文涉及的模型,在概念上對於兩者是一致的。

同時此文中,我們將只談論明確的合作型多任務處理機制--回調callbacks),因為這是非同步框架實現中最廣泛,最常用的選項。


現代應用程序最常見的活動是處理輸入和輸出,而不是大規模的數字運算。使用輸入/輸出(I/O)功能的問題在於它們是阻塞的。與CPU的計算速度相比,硬碟的寫入和網路的讀取操作需要非常長的時間。在這些任務完成之前,應用的功能不會結束,在此期間,你的應用程序並不會進行任何操作。對性能有高要求的應用程序而言,這是主要的瓶頸,因為其他活動和其他I / O操作都在等待。

標準解決方案之一是使用線程。每個阻塞I/O操作都在一個單獨的線程中啟動。當一個線程在執行阻塞功能時,處理器可以調度另一個線程去執行其餘任務,這樣做的話實際上需要CPU。

在這篇文章中,我們將大致討論同步和非同步的概念。

Synchronicity(同步)

在同步概念中,單個線程被分配給單個任務並開始處理它。當一個任務完成後,線程執行下一個任務並進行相同的操作:它一個接一個地執行所有命令以完成一個指定的任務。在這個系統中,線程不能中途離開任務並繼續下一個任務。因此,我們可以確定:無論何時何地執行某個功能 - 它都無法設置為保持狀態,並且將在完整結束一個任務才開始執行另一個功能(可以利用當前功能的作用,改變數據)。

Single-threaded(單線程)

如果系統是單線程執行的,並且有幾個任務與之相關,那麼它們將一個接一個地順序執行。

如果任務總是按照指定的順序執行,在邏輯上做一個明確的簡化:假設後續任務執行時,所有前期任務都已完成,沒有錯誤發生,所有輸出都可供使用。

如果其中一個命令很慢,整個系統將等待此命令完成 -- 單線程執行沒有辦法繞開此問題。

Multi-threaded(多線程)

在多線程系統中,上面提到的原則得以保留--單個線程被分配給單個任務並且此線程執行任務直到任務完成。

但是在此係統中,每個任務都在一個獨立的控制線程中執行。這個控制線程可能由操作系統管理,可能在具有多個處理器或多核的系統上,並行運行,也可能在單個處理器上交錯運行。

那麼現在我們有多個線程,同時有多個任務(不是一項任務,而是幾項不同的任務)可以並行執行。通常,任務需要的處理時間是不同的,事實上,已經完成其中一個任務的線程可以轉到下一個。

多線程程序更複雜,也更容易出錯。常見的麻煩有:競爭條件(race-condition),死鎖(dead-locks)和資源匱乏(resource starvation)。

Asynchrony(非同步)

其他實現採用另外一種風格--非同步,無阻塞風格。非同步是一種並發編程的風格,但不是並行。

大多數現代操作系統都提供事件通知子系統(event notification subsystems)。例如,在介面上調用普通的讀取操作(read)將阻塞,直到發送方真正發送了一些內容。反之,應用程序可以請求操作系統監聽介面,並將一個事件通知放入特定隊列中。應用程序可以方便地檢查事件(可能為了高效使用處理器,會提前進行一些數值運算操作)同時提取數據。它是非同步的,因為應用程序在某一點表達興趣,然後在另一點使用數據(在時間維度和空間維度)(原文:the application expressed interest at one point, then used the data at another point (in time and space))。它是非阻塞的,因為應用程序的線程是自由,可以執行其他任務。

非同步代碼從主應用程序線程中刪除阻塞操作,以便於可以持續執行,但是在時間上稍等一會(或者可能在空間上的其他地方),處理程序可以進一步處理阻塞操作。簡單來說,主線程設置任務並將其執行轉移到以後的某個時段(或另一個獨立的線程)。

Asynchrony and context switching(非同步和上下文切換)

雖然非同步編程可以預防上面提到的問題(競爭條件,死鎖和資源匱乏),但實際上,它是針對一個完全不同的問題而設計的:CPU上下文切換。當您同時運行多個線程時,每個CPU內核仍然只能一次運行一個線程。為了允許所有線程/進程共享資源,CPU經常切換上下文。CPU設計為了精簡邏輯,會間隔一段隨機時間,保存一個線程的所有上下文信息並切換到另一個線程。意味著CPU會以非確定的間隔在你的線程之間不斷切換。線程也是資源,它們不是免費的。

非同步編程本質上是帶有用戶空間線程的合作型多任務處理機制,應用程序在用戶空間線程中管理線程和上下文切換,而不是CPU。基本上,在非同步世界中,上下文切換僅發生在定義好的切換點,而不是非確定性的時間間隔。

Comparison(對比)

對比同步模型,非同步模型在以下情況表現更好:

  • 有大量的任務,所以總會有至少一項任務可以取得進展;
  • 這些任務會執行大量的I/O操作,導致同步程序在其他阻塞任務運行時,將浪費大量時間;
  • 這些任務在很大程度上是彼此獨立的,因此幾乎不需要進行任務間通信(同時,由於這個原因,一項任務要等待另一項任務的結果)。

這些情況幾乎完美的展示了在客戶端--伺服器環境下,繁忙伺服器(例如web伺服器)的經典特徵。每個任務以接收請求和發送回復的形式,表示一個帶有I/O操作的客戶端請求。伺服器實現是非同步模型的主要候選項,這就是Twisted和Node.js以及其他非同步伺服器庫,近年來變得如此受歡迎的原因。

為什麼不使用更多的線程呢?如果一個線程在I/O操作上阻塞,另一個線程可以取得進展,對不對?但是,隨著線程數量的增加,您的伺服器可能會遭遇性能問題。對於每個新線程,都需要一些內存開銷--與線程狀態的創建和維護相關聯的內存開銷。非同步模型另一個提高性能的表現是它避免了上下文切換--操作系統每次將控制從一個線程轉移到另一個線程時,它必須保存所有相關的寄存器(registers)、記憶圖(memory map)、堆棧指針(stack pointers)、CPU上下文(CPU context)等等,以便於其他線程可以在中斷的地方繼續執行。這樣做的開銷可能非常大。

Event loop(事件循環)

如果執行線程忙於處理其他任務,新任務到達的事件如何通知應用程序?實際上,操作系統有許多線程,實際與用戶交互的代碼與我們的應用程序分開執行,只嚮應用程序發送通知。

那麼如何管理所有的事件線程呢?在特定的事件循環中

事件循環正如它的字面意思一樣,有一個事件隊列(Task Queue)(所有已觸發的事件都存儲在其中--上圖中被稱為任務隊列)和一個循環(Event Loop),此循環不斷地將這些事件從隊列中拉出並執行這些事件的回調(所有的命令都在調用堆棧(Call Stack)上執行)。API表示非同步功能調用的API,例如等待來自客戶端或資料庫的響應。

因此,所有操作首先進入調用堆棧Call Stack),而非同步指令進入API,等指令動作完成後,其需要的回調操作進入任務隊列Task Queue)。然後再次在調用堆棧上執行。

此進程的協調(Coordination)發生在事件循環Event Loop)中。

你看,這與我們上一篇文章中討論的反應器模式有什麼不同?對--完全一致。

當事件循環形成應用程序的中央控制流程結構時,它或許被稱為主循環或主事件循環。這一稱呼是名副其實,因為這樣的事件循環處於應用程序內的最高控制級別。

事件驅動的編程中,應用程序表達對某些事件的興趣,並在事件發生時對它們做出響應。事件循環負責從操作系統中收集事件或監聽其他事件源,開發者可以註冊在事件發生時需要調用的回調。事件循環通常會一直運行。

JS事件循環概念解釋:

雖然挺煩的,但事件循環到底是什麼呢?|Philip Roberts|JSConf EU?

youtu.be

Conclusion(結論)

總結一下整個理論系列:

  1. 應用程序中的非同步操作可以使其更高效,最重要的是對用戶來說更快。
  2. 節省資源。操作系統線程比進程便宜,但一個任務使用一個線程仍然非常昂貴。復用它會更高效--這也是非同步編程為我們提供的能力。
  3. 對於I/O密集型應用的優化和擴展,非同步編程是最重要的技術之一。(沒錯--非同步編程對於計算密集型(CPU-bound)任務沒有幫助)。
  4. 非同步編程對於程序員而言,編寫不易,調試不易。

此係列還剩最後一篇文章,是關於Python3.5+中,非同步編程相關概念的實現。由於本人對Python不甚瞭解,對此係列的翻譯到此文為止,感興趣的朋友可以直接閱讀原文。

Asynchronous programming. Python3.5+?

luminousmen.com圖標
推薦閱讀:

相關文章