Rust Async 1- 異步編程的概念

異步編程的概念

在計算機技術領域,併發(concurrency)是指程序不同部分可以同時不按順序的執行且不影響最終結果的能力。

嚴格點說,程序的不同部分不按順序執行就是併發(concurrency),而同時執行多個任務是並行(parallelism)。

但在本文中,我們用併發一詞代表 concurrency 和 parallelism。

爲什麼要讓程序不按順序執行呢?有兩個主要原因:

1.  用戶的需求:需要讓軟件運行的更快;

2.  硬件提供的能力:多 CPU 或多核 CPU 的計算機,讓程序開發人員可以編寫出整體性能更好的程序。

爲了更好的利用 CPU 時間,需要理解軟件程序處理任務的兩種類型:

  1.  CPU 密集型:佔用 CPU 資源的任務,例如文件壓縮、視頻編碼、圖形處理和計算加密證明。

  2. IO 密集型:佔用 IO 資源的任務,例如從文件系統或數據庫訪問數據,以及處理網絡 TCP/HTTP 請求。

對於 CPU 密集型的任務,通常可以利用多 CPU 或多核進行處理。

對於 IO 密集型,我們以 Web 項目中處理請求的任務爲例。在 Web 項目中,我們通過 CRUD 操作把數據從數據庫裏傳遞過來,這就要求 CPU 等待數據寫入到磁盤,但磁盤很慢。所以,如果程序從數據庫請求 10000 筆數據,它會向操作系統請求磁盤的訪問,而與此同時,CPU 就是在乾等。那麼程序員應該怎麼利用 CPU 的這段等待的時間?那就是讓 CPU 執行其它的任務。

另一個典型的例子就是網絡請求的處理,客戶端建立連接發送請求,服務器端處理請求返回響應並關閉連接。如果 CPU 還在處理前一個請求,而第二個的請求卻已經到達,那麼第二個的請求必須在隊列中等待着第一個請求處理完成嗎?或者我們可以把第二個請求安排到其他可用的 CPU 或內核上?

我們先看看同步、多線程、異步的區別:

備註:這裏面 Task 1 包含三個步驟:

1.  處理數據

2.  一個阻塞操作

3.  把從任務要返回的數據進行打包

我相信您能看明白上圖,我這裏只簡單介紹一下:

1.  **同步執行。**執行完 Task 1 的第一步處理數據後,CPU 等着 Task 1 的阻塞操作,然後再執行後續的步驟;

2.  **多線程。**包含阻塞操作的 Task 1 被安排在單獨的系統線程執行,其它的任務在另外的線程中執行;

3.  **異步。**它會執行 Task 1 直到它開始阻塞等待 I/O。這時異步運行時(例如 Tokio)會安排執行 Task 2,而當 Task 1 的阻塞操作結束時,Task 1 又被安排到 CPU 上來運行。

但哪一種方式更好,還要看實際情況。

再看一個 Web 服務器同時接收多個請求的例子。

 第一種方式是使用多線程,針對每個請求都開啓一個原生的系統線程。這確實會提高性能,但卻引入了新的複雜性:

此外,多線程還有另外一個問題。多線程分爲兩種模型:

而 Rust 標準庫實現的是 1:1 模型。一般來說操作系統對總線程數都有限制,它受到棧內存和虛擬內存量影響。而且線程切換時還有上下文切換的成本和管理線程的其他資源成本。

所以多線程並不適應所有的場景,對於上述場景它並不完美。

上圖採用的是併發(concurrent)方式,或者叫異步編程。每個 HTTP 請求被異步 Web Server 接收,Web Server 會生成一個任務來處理它,並由異步運行時各類異步任務在可用的 CPU 上執行。很明顯在該場景下,異步的方式更加合適。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/3PXyKzl1hdDUDZnyNYC3Mw