在 Rust 裡使用非同步(Asynchronous)

閱讀時間約 8 分鐘

非同步程式設計(Asynchronous programming) 或是簡單的稱之為 async,它是一種並發程式模型(concurrent programming model),其目的就是讓多個任務能同時在作業系統的執行緒上執行,並透過 async/.await 保留同步。

Sync 與 Async 簡單的分別:

  • 同步(Sync):做完一件事情後再做下一件
  • 非同步(Async):一件事情還沒完成時,可以先做其他不衝突的事情
    • 並發、並行(Concurrency):在同個程式下,各個任務可獨立執行平行(Parallelism):同時執行多個程式
    • 平行(Parallelism):同時執行多個程式

Async 與其他並發程式模型

根據不同程式語言所支持的並發程式模型,以下列出幾種常見的幾種:

  • 作業系統執行緒(OS threads):不需要改變任何程式模型,就可以簡單的達成並發,但是,各個執行緒之間要同步就很困難,而且需要更多的效能,不夠應付大量密集的 IO 工作
  • 事件驅動程式設計(Event-driven programming):最相關的關鍵詞就是 callback,高效率但語法很容易過冗,而且很難追蹤資料流與錯誤的問題
  • 協程(Coroutines):與 threads 一樣不需要改變任何程式模型,就可以簡單的達成並發。與 async 一樣可支援大量任務,不過它抽象化了許多系統程式設計與自訂運行的低階細節
  • 演員模型(Actor Model):將所有並發計算切成 actor 的單元,並透過 message 溝通,很像分散式系統。不過有很多問題待解決,例如:流量控制、邏輯問題

與上述做比較,在 Rust 等低階程式語言裡,使用非同步程式設計可以實現高性能並同時提供 threads 與 coroutines 的各式優點。

在 Rust 裡的 Async 與 threads

如果在 Rust 使用 OS threads,可以使用 std::threads 或間接訪問 threads pool。

OS threads

OS threads 適合少量任務,執行緒會使用到 CPU 與記憶體,生成 threads 和在 threads 之間切換是很耗效能的,連空閒的 threads 也會消耗效能,雖然使用 threads pool 可以減輕一些使用成本,但也不是全部。

Threads 能讓你重複使用已存在的同步程式碼,而不需要大幅的改變程式,也不需要特定的設計模型,在一些特定的 OS 中,你還可以設定 threads 的優先級來決定誰先執行。

Async

Async 明顯的減少 CPU 與記憶體的消耗,特別是對於具有大量密集的 IO 工作,例如:伺服器和資料庫。在同等的條件下,使用 async 可以處理比 OS threads 更多的任務,這是因為 async 使用了少量的 threads 來處理大量的簡單任務。

最後要註明一點,並不是 async 比 threads 好,而是要根據當下的狀況來選擇使用,如果你不需要 async 的功能,直接使用 threads 反而是更簡單的方式。

實例比較

建立兩個 threads 來同時下載兩個網頁:

fn get_two_sites() {
// 產生兩個 threads
let thread_one = thread::spawn(|| download("https://www.foo.com"));
let thread_two = thread::spawn(|| download("https://www.bar.com"));

// 等待 threads 完成工作
thread_one.join().expect("thread one panicked");
thread_two.join().expect("thread two panicked");
}

不過這種簡單的任務建立兩個 threads 是很浪費的,在大型的應用程式中每一點資源都是很重要的,所以在這裡,我們可以選擇 Rust 中的 async 方式,既不需要使用額外的執行緒,還可以達成並發的功能:

async fn get_two_sites_async() {
// 建立兩個不同的 futures,當運行程式會使用 async 的方式來下載
let future_one = download_async("https://www.foo.com");
let future_two = download_async("https://www.bar.com");

// 同時執行兩個 futures
join!(future_one, future_two);
}

這樣寫的話就不會產生額外的 threads,當然唯一的重點就是要自己完成 download_async 的非同步程式碼。

async/.await

async/.await 是 Rust 內建將非同步編寫成像同步程式碼的方式,async 會將這個指定的區塊轉成一個狀態機制(state machine),稱為 Future

Future 是 Rust 裡非同步(Async) 任務的狀態:

  • poll:不斷查詢任務是否完成
    • Pending:任務還在執行Ready(val):如果成功完成任務,則連同結果一起回傳
    • Ready(val):如果成功完成任務,則連同結果一起回傳

實際操作

這裡使用教學裡的 futures 來實現非同步程式碼,首先新增依賴項到 Cargo.toml

[dependencies]
futures = "0.3"

接著在更改 src/main.rs

// `block_on` 會阻擋目前的 thread 直到 future 完成
use futures::executor::block_on;

async fn hello_world() {
println!("hello, world!");
}

fn main() {
let future = hello_world();
block_on(future);
}

這邊來更改使用 async/.await 來寫:

use futures::executor::block_on;
use std::{thread, time::Duration};

async fn read_book() {
// 設定一個等待時間
thread::sleep(Duration::from_secs(1));
}

async fn tell_story() {
// 等待完成後才會繼續
read_book().await;
println!("Book content");
}

async fn hand_move() {
println!("Hand move");
}

async fn async_main() {
let f1 = tell_story();
let f2 = hand_move();

// `join!` 類似於 `.await` 可以一次等待多個 futures
futures::join!(f1, f2);
}

fn main() {
block_on(async_main());
}

根據以上的例子,要先讀一本書的內容才能開始說故事,在說故事的期間同時還會有一些肢體動作,如果沒有先讀那本書,後面的動作則無法成立。

結語

以上是一個簡單的 Rust 實作 Async 的方式,在依賴項的部分,你也可以使用更多人在用的 tokio 來實現非同步。

Reference

avatar-img
2會員
4內容數
簡單的了解一個關於程式相關的主題
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Soft Code 的其他內容
對於剛學習 Rust 的人來說,最一開始不是看 Youtube 的影片或是跟著 Rust 官方手冊 The Book 學習,這邊來推薦一個練習 Rust 的專案,名為 rustlings。 rustlings 包含著一些簡單的題目,來讓你更習慣閱讀或編寫 Rust 程式碼。該專案會在每道題目上給予
Bun 在 2023/9/8 釋出了 1.0 版本,這篇文章就來說一下為什麼 Bun 會被關注,還有該怎麼使用它。
對於剛學習 Rust 的人來說,最一開始不是看 Youtube 的影片或是跟著 Rust 官方手冊 The Book 學習,這邊來推薦一個練習 Rust 的專案,名為 rustlings。 rustlings 包含著一些簡單的題目,來讓你更習慣閱讀或編寫 Rust 程式碼。該專案會在每道題目上給予
Bun 在 2023/9/8 釋出了 1.0 版本,這篇文章就來說一下為什麼 Bun 會被關注,還有該怎麼使用它。
你可能也想看
Google News 追蹤
Thumbnail
徵的就是你 🫵 超ㄅㄧㄤˋ 獎品搭配超瞎趴的四大主題,等你踹共啦!還有機會獲得經典的「偉士牌樂高」喔!馬上來參加本次的活動吧!
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
sync rst 和 async rst reg 在rtl上只有差在always block的condition不同,但是在合成上卻是兩種不同type的register,有著不同的優缺 sync reset reg : always @ (postedge clk) begin if (!rs
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
分享一個有趣的套件,名為 await-to-js。 可以讓 Promise 與 await 的寫法更簡潔。
Thumbnail
簡要說明 JavaScript 的 Event Loop JavaScript 是單執行緒 (single-threaded) 語言,這意味著它一次只能執行一件事,因此所有函式都需要排隊等待執行,這被稱為同步 (synchronous)。在同步操作中,若函式過多或過於複雜,會導致程式阻塞 (blo
※ 非同步概念總複習 為什麼要使用 Promise? 在 JavaScript 開發中,處理非同步操作是常見需求,涉及如文件讀寫、數據庫查詢或網路請求等耗時任務。傳統的回調方式可能導致代碼結構混亂,稱為「回調地獄」,難以維護和理解。 Promise 是解決這問題的方法。它是一個物件(objec
認識 async/await基本概念: async 的本質是 promise 的語法糖 ,只要 function 標記為 async,就表示裡頭可以撰寫 await 的同步語法,而 await 顧名思義就是「等待」,它會確保一個 promise 物件都解決 ( resolve ) 或出錯 ( re
什麼是 Promise.all? 在有多個 Promise 的時候,使用 Promise.all 可以確保「所有的 Promise 都執行完以後,才進入 then」。 Promise.all 語法結構: Promise.all 接受的參數是陣列形式。 什麼時候要使用 Promise.all?
※ Promise基本介紹 什麼是 Promise? Promise 是 JavaScript 的一個構造函式,用於創建表示非同步操作的物件實例。使用 new Promise() 時,你會創建一個包含非同步操作的實例,這個實例可以透過其繼承的方法如 then(), catch(), 和 fina
※ 同步概念: 單純地「由上而下」執行程式碼,而且一次只執行一件事,也就是「按順序執行,一個動作結束才能切換到下一個」。缺點是你需要「等待」事情執行完畢,才能繼續往下走。 ※ 非同步概念: 盡可能讓主要的執行程序不需要停下來等待,若遇到要等待的事情,就發起一個「非同步處理」,讓主程序繼續執行,
Thumbnail
為什麼需要非同步? 我們在「【Web微知識系列】 Web Workers」有介紹到在瀏覽器可執行腳本Javascript環境底下如何完成非同步的操作, 主要是為了讓任務更有效率的進行, 不會因為一個非常耗時的工作堵塞住整個服務, 導致無法服務他人的窘境。 大家應該經常在餐廳裡會看到服務員協
Thumbnail
徵的就是你 🫵 超ㄅㄧㄤˋ 獎品搭配超瞎趴的四大主題,等你踹共啦!還有機會獲得經典的「偉士牌樂高」喔!馬上來參加本次的活動吧!
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
sync rst 和 async rst reg 在rtl上只有差在always block的condition不同,但是在合成上卻是兩種不同type的register,有著不同的優缺 sync reset reg : always @ (postedge clk) begin if (!rs
Thumbnail
在網路速度有限的情況下,依序記錄不斷產生的資訊,能統計使用者在頁面上操作了哪些功能。
Thumbnail
分享一個有趣的套件,名為 await-to-js。 可以讓 Promise 與 await 的寫法更簡潔。
Thumbnail
簡要說明 JavaScript 的 Event Loop JavaScript 是單執行緒 (single-threaded) 語言,這意味著它一次只能執行一件事,因此所有函式都需要排隊等待執行,這被稱為同步 (synchronous)。在同步操作中,若函式過多或過於複雜,會導致程式阻塞 (blo
※ 非同步概念總複習 為什麼要使用 Promise? 在 JavaScript 開發中,處理非同步操作是常見需求,涉及如文件讀寫、數據庫查詢或網路請求等耗時任務。傳統的回調方式可能導致代碼結構混亂,稱為「回調地獄」,難以維護和理解。 Promise 是解決這問題的方法。它是一個物件(objec
認識 async/await基本概念: async 的本質是 promise 的語法糖 ,只要 function 標記為 async,就表示裡頭可以撰寫 await 的同步語法,而 await 顧名思義就是「等待」,它會確保一個 promise 物件都解決 ( resolve ) 或出錯 ( re
什麼是 Promise.all? 在有多個 Promise 的時候,使用 Promise.all 可以確保「所有的 Promise 都執行完以後,才進入 then」。 Promise.all 語法結構: Promise.all 接受的參數是陣列形式。 什麼時候要使用 Promise.all?
※ Promise基本介紹 什麼是 Promise? Promise 是 JavaScript 的一個構造函式,用於創建表示非同步操作的物件實例。使用 new Promise() 時,你會創建一個包含非同步操作的實例,這個實例可以透過其繼承的方法如 then(), catch(), 和 fina
※ 同步概念: 單純地「由上而下」執行程式碼,而且一次只執行一件事,也就是「按順序執行,一個動作結束才能切換到下一個」。缺點是你需要「等待」事情執行完畢,才能繼續往下走。 ※ 非同步概念: 盡可能讓主要的執行程序不需要停下來等待,若遇到要等待的事情,就發起一個「非同步處理」,讓主程序繼續執行,
Thumbnail
為什麼需要非同步? 我們在「【Web微知識系列】 Web Workers」有介紹到在瀏覽器可執行腳本Javascript環境底下如何完成非同步的操作, 主要是為了讓任務更有效率的進行, 不會因為一個非常耗時的工作堵塞住整個服務, 導致無法服務他人的窘境。 大家應該經常在餐廳裡會看到服務員協