Rust入門之并發編程基礎(三)

Rust入門之并發編程基礎(三)

題記:6月底7月初,結束北京的工作生活回到二線省會城市發展了,鴿了較久了,要繼續堅持學習Rust,堅持寫博客。

背景

我們平時使用計算機完成某項工作的時候,往往可以同時執行多個任務,比如可以編程的時候同時播放音樂,就算是單核CPU也是如此。這是因為現代計算機操作系統會使用**“中斷機制”**來執行任務,任務可以分為:

  • “CPU 密集型“或者“計算密集型”
  • ”IO 密集型“

根據這兩種類型,又可以有針對性的利用操作系統的”中斷機制“提供計算機同時執行多任務的效率。大多數函數調用都是會發生阻塞的,等待當前執行完成才會繼續執行后續的動作,如果在一個程序中有多個任務,多個任務中某些任務阻塞時可以不影響其他任務執行,也就是異步執行多個任務,這樣效率就會提高很多了。多線程異步執行程序就顯得尤為重要了。

討論一下并發和并行

比如給員工分配任務,如果分配給一個員工多個任務,這個員工在任務一個任務完成前同時處理多個任務,這就類似計算機操作系統的中斷機制,這就是并發

當將多個任務在多個員工中分配執行,每個員工分配一個任務并單獨處理它,這就是并行。每個組員可以真正的同時進行工作。

如果一個任務執行必須依賴另一個任務,那么任務之間必須串行的執行,一個處理了再處理下一個任務。并行和并發也會發生相互交叉(阻塞)如果某個程序中的某幾個任務的并發執行都需要等待另一個任務的完成,可能就會集中時間做這個任務,那么就都無法并行工作了。

并發 VS 并行

特征并發并行串行
執行方式任務交替執行任務同時執行順序執行
類比模型單員工輪流處理多個任務多員工各處理單個任務單任務完成后才能開始下一個
資源需求單核或多核多核必需單核或多核
執行時機任務可能重疊任務真正同時任務順序執行

同樣的基礎動作也作用于軟件和硬件。在單核CPU的計算機上,CPU一次只能執行一個操作,不過它仍然是并發工作,借助像線程,進程和異步(async)等工具,計算機可以暫停一個活動,并在最終切換回第一個活動執行之前切換到其它活動。在一個多核CPU的計算機上,它也可以并行工作,一個核心執行一個任務,同時另一個核心可以執行其他不相關的工作,而且這些工作實際上是同時發生的。

Rust 異步編程主要處理并發性,取決于硬件、操作系統和所使用的異步運行時(async runtime),并發也有可能在底層使用了并行。下面將詳細的討論Rust 異步編程是如何工作的。

Rust 異步編程的核心組件:future、async、await

Rust 異步編程的三個重要關鍵元素:

  1. futures
  2. async 關鍵字
  3. await 關鍵字

future 是一個現在還沒準備好,未來會返回結果的一個值。類似Java 語言中也有類似的Future概念。Rust提供了 Future trait 作為基礎組件。

async 關鍵字可以用于代碼塊或函數,表明它們可以被中斷或恢復。在一個async 塊或者 async 函數中,可以使用 await 關鍵字來等待一個 future 準備就緒,這個過程稱之為等待一個 future。每一個等待future 的地方都可能是一個async 塊或async函數中斷并隨后恢復的點。檢查一個future 并查看其值是否準備就緒的過程被稱之為輪詢(polling)。

future 的特點:

  • Rust編譯器將 async/await 代碼轉換為使用 Future trait 的等效代碼
    • 類似 for 循環被轉換為使用 Iterator trait
  • 開發者可以為自定義數據類型實現 Future trait
    • 提供統一的接口但允許實現不同的異步操作實現

Rust 官方為了大家學習實驗異步操作,創建了一個 trpl crate(trpl 是 “The Rust Programming Language” 的縮寫)。它重導出了你需要的所有類型、traits 和函數,它們主要來自于 futurestokio crates。

  • futures crate 是一個 Rust 異步代碼實驗的官方倉庫,也正是 Future 最初設計的地方。
  • Tokio 是目前 Rust 中應用最廣泛的異步運行時(async runtime),特別是(但不僅是!)web 應用。這里還有其他優秀的運行時,它們可能更適合你的需求。我們在 trpl 的底層使用 Tokio 是因為它經過了充分測試且廣泛使用。

接下來上代碼,利用 trpl 提供的多種組件來編寫第一個異步程序。我們構建了一個小的命令行工具來抓取兩個網頁,拉取各自的 <title> 元素,并打印出第一個完成全部過程的標題。先創建一個rust項目,添加 trpl 庫。

Cargo.toml:

[package]
name = "hello-async"
version = "0.1.0"
edition = "2021"[dependencies]
trpl = "0.2.0"

main.rs:

use trpl::Html;
use trpl::Either;async fn page_title(url: &str) -> (&str, Option<String>) {// 傳入的任意 URL,使用 await 等待響應,因為Rust的futures是惰性的,只有調用await時,才會執行異步操作let response = trpl::get(url).await;let response_text = response.text().await;let title = Html::parse(&response_text).select_first("title").map(|title_element| title_element.inner_html());(url, title)
}fn main() {// 接收參數,兩個參數分別是兩個URLlet args: Vec<String> = std::env::args().collect();trpl::run(async {let title_fut_1 = page_title(&args[1]);let title_fut_2 = page_title(&args[2]);let (url, maybe_title) =match trpl::race(title_fut_1, title_fut_2).await {Either::Left(left) => left,Either::Right(right) => right,};println!("{url} returned first");match maybe_title {Some(title) => println!("Its page title is: '{title}'"),None => println!("Its title could not be parsed."),}});
}

async 修飾 page_title 函數,說明這個函數是一個異步函數。trpl::get(url) 去調用url地址返回響應,這里需要等待時間,這個函數也是用 async 修飾了表示它也是一個異步函數并返回future,這里加上await,表示我們要等待這個future 返回響應。同樣response.text() 也是異步的,這里也使用 await 等待返回結果。 響應文本拿到后再使用Html::parse 解析。

這里要注意因為Rust的futures是惰性的,只有調用await時,才會執行異步操作,然后這里也可以改為鏈式調用,讓代碼更加簡潔。

page_title 這個函數使用了async修飾,當函數使用async的時候,就會將函數轉換為返回Future的普通函數。

這個示例分別由用戶提供的 URL 調用 page_title 開始。我們將調用 page_title 產生的 future 分別保存為 title_fut_1title_fut_2。請記住,它們還沒有進行任何工作,因為 future 是惰性的,并且我們還沒有 await 它們。接著我們將 futures 傳遞給 trpl::race,它返回一個值表明哪個傳遞的 future 最先返回。

并發與async

使用異步編程解決一些并發問題,這里更多關注線程與future的區別。

代碼示例:

use std::time::Duration;fn main() {trpl::run(async {trpl::spawn_task(async {for i in 1..10 {println!("hi numnber {i} from the first task!");trpl::sleep(Duration::from_millis(500)).await;}});for i in 1..5 {println!("hi number {i} from the second task!");trpl::sleep(Duration::from_millis(500)).await;            }});
}

執行結果:

hi number 1 from the second task!
hi numnber 1 from the first task!
hi number 2 from the second task!
hi numnber 2 from the first task!
hi number 3 from the second task!
hi numnber 3 from the first task!
hi number 4 from the second task!
hi numnber 4 from the first task!
hi numnber 5 from the first task!

根據執行結果可以看出。first task 在 second task 執行結束后也停止了,這是因為主任務(second task)已經停止,在主任務中創建的異步任務(first task)也會停止。如果要運行first task 直到結束,就需要一個join(join handle)來等待第一個任務完成。對于線程來說,可以使用join 方法來阻塞直到線程結束運行。在這里可以使用await 達到相同的效果。

添加handle.await.unwrap():

use std::time::Duration;fn main() {trpl::run(async {let handle = trpl::spawn_task(async {for i in 1..10 {println!("hi numnber {i} from the first task!");trpl::sleep(Duration::from_millis(500)).await;}});for i in 1..5 {println!("hi number {i} from the second task!");trpl::sleep(Duration::from_millis(500)).await;            }handle.await.unwrap();});
}	

執行結果:

hi number 1 from the second task!
hi numnber 1 from the first task!
hi number 2 from the second task!
hi numnber 2 from the first task!
hi numnber 3 from the first task!
hi number 3 from the second task!
hi number 4 from the second task!
hi numnber 4 from the first task!
hi numnber 5 from the first task!
hi numnber 6 from the first task!
hi numnber 7 from the first task!
hi numnber 8 from the first task!
hi numnber 9 from the first task!

消息傳遞

再使用前面講過的消息傳遞的例子,這次使用 future 演示線程間消息傳遞,來看看基于 future 的并發和基于線程的并發的差異。

trpl 中的 rx.recv() 返回一個future,是異步的。之前我們使用let s = rx.recv(); 是同步阻塞的。

let s: Result<String, mpsc::RecvError> = rx.recv();

代碼示例:

fn main() {trpl::run(async {let (tx, mut rx) = trpl::channel();let val = String ::from("hi");tx.send(val).unwrap();// trpl channel rx.recv() 返回的是一個future, 是異步非阻塞版本let received = rx.recv().await.unwrap();println!("get: {received}");});}

上面的代碼中,發送到接收都是順序執行的也就是同步的,因為它們都在同一個async 代碼塊當中。接下來修改一下代碼,我們發送多個消息,讓多個消息異步發送和接收,而不是都發送完才可以接收。

將發送端和接收端分別放到各自的async 塊中,返回兩個future,再使用trpl::join(),返回一個新的future.,再調用await等待兩個future完成。

代碼示例:

use std::time::Duration;fn main() {trpl::run(async {let (tx, mut rx) = trpl::channel();// 發送放到一個future 中let tx_future = async move {let vals = vec![String::from("Hi"),String::from("from"),String::from("the"),String::from("future"),];for val in vals {tx.send(val).unwrap();trpl::sleep(Duration::from_millis(500)).await;}};let rx_future = async {while let Some(value) = rx.recv().await {println!("received: {value}");}};// 使用 join 接收兩個future,返回一個新的futuretrpl::join(tx_future, rx_future).await;});}

執行結果:

每隔500 ms 接收一個消息并打印。

received: Hi
received: from
received: the
received: future

let tx_future = async move { 這里使用了move 關鍵字,將 tx 移動(move)進異步代碼塊,它會在代碼塊結束后立刻被丟棄,這樣tx銷毀了,rx 也就在接收后優雅的關閉。

多生產者代碼示例:

use std::time::Duration;fn main() {trpl::run(async {let (tx, mut rx) = trpl::channel();let tx1 = tx.clone();// 發送放到一個future 中let tx1_future = async move {let vals = vec![String::from("Hi"),String::from("from"),String::from("the"),String::from("future"),];for val in vals {tx1.send(val).unwrap();trpl::sleep(Duration::from_millis(500)).await;}};let rx_future = async {while let Some(value) = rx.recv().await {println!("received: {value}");}};let tx_future = async move {let vals = vec![String::from("Hi"),String::from("from"),String::from("the"),String::from("future"),];for val in vals {tx.send(val).unwrap();trpl::sleep(Duration::from_millis(500)).await;}};// 使用 join 接收兩個future,返回一個新的futuretrpl::join3(tx1_future, tx_future, rx_future).await;});}

后續

本文討論了并發和人并行的區別,也講了 future,await 再異步編程中的作用,future 代表未來會返回結果值的一個變量,await表示要等待future返回結果。

本文記錄根據Rust程序設計語言(Rust 中文社區翻譯)學習筆記,但是發現這個網頁版電子書,異步這里講的很抽象,后續經過更深入的學習會再更新異步編程的部分。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/89507.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/89507.shtml
英文地址,請注明出處:http://en.pswp.cn/web/89507.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

一文讀懂循環神經網絡—深度循環神經網絡(DRNN)

目錄 一、從 RNN 到 DRNN&#xff1a;為什么需要 “深度”&#xff1f; 二、DRNN 的核心結構 1. 時間維度&#xff1a;循環傳遞 2. 空間維度&#xff1a;多層隱藏層 3. 雙向 DRNN&#xff08;Bidirectional DRNN&#xff09; 三、DRNN 的關鍵挑戰與優化 1. 梯度消失 / 爆…

磁懸浮軸承系統中由不平衡力引發的惡性循環機制深度解析

磁懸浮軸承系統中由不平衡力引發的 “振動-激勵-更大振動”惡性循環 是一個典型的 正反饋失控過程,其核心在于 傳感器信號的污染 與 控制器對真實位移的誤判。以下是其逐步演進的原理詳解: 惡性循環的觸發與演進 1:不平衡力的產生(根源) 轉子存在質量偏心,質心(CM)偏離…

優迅股份IPO隱憂:毛利水平“兩連降”,研發費用率不及行業均值

撰稿|行星來源|貝多財經近日&#xff0c;廈門優迅芯片股份有限公司&#xff08;下稱“優迅股份”&#xff09;的科創板IPO審核狀態變更為“已問詢”&#xff0c;中信證券為其保薦機構。天眼查App信息顯示&#xff0c;優迅股份成立于2003年2月&#xff0c;是中國首批專業從事光通…

Linux探秘坊-------15.線程概念與控制

1.線程概念 1.什么是線程2.線程 vs 進程不同的操作系統有不同的實現方式&#xff1a; linux &#xff1a;直接使用pcb的功能來模擬線程&#xff0c;不創建新的數據結構windows&#xff1a; 使用新的數據結構TCB&#xff0c;來進行實現&#xff0c;一個PCB里有很多個TCB 3.資源劃…

Github庫鏡像到本地私有Gitlab服務器

上一節我們看了如何架設自己的Gitlab服務器&#xff0c;今天我們看怎么把Github庫轉移到自己的Gitlab上。 首先登錄github&#xff0c;進入自己的庫復制地址。 克隆鏡像庫 在本地新建一個文件夾 在文件夾執行CMD指令 git clone --mirror gitgithub.com:thinbug/A.git–mirror參…

【C++】——類和對象(中)——默認成員函數

一、類的默認成員函數默認成員函數就是用戶沒有顯示實現&#xff0c;不過編譯器會自動生成的成員函數&#xff0c;稱為默認成員函數。一個類默認成員函數一共有6個&#xff0c;在我們不寫的情況下&#xff0c;編譯器就會自動生成這6個成員函數&#xff0c;不過我們重點要學習的…

MATLAB知識點總結

1.將A圖與B圖相同范圍內歸一化顯示在同一個figure上&#xff1a; figure, plot(A(150:450,500)/max(A(150:450,500))) hold on plot(D(150:450,500)/max(D(150:450,500)),‘R’) 將兩幅圖像的一定范圍顯示在同一圖像上。 figure,plot(A(350,100:450)) hold on plot(G(350,100:4…

易天光通信10G SFP+ 1550nm 120KM 雙纖光模塊:遠距離傳輸的實力擔當

目錄 前言 一、10G SFP雙纖光模塊概述 二、易天10G SFP 120KM 雙纖光模塊核心優勢與應用 核心優勢&#xff1a; 主要關鍵應用如下&#xff1a; 三、易天10G SFP 120KM 雙纖光模塊客戶優勢 總結 關于易天 前言 在構建高效穩定的網絡架構時&#xff0c;10G SFP 光模塊 12…

【深度學習】神經網絡 批量標準化-part6

九、批量標準化是一種廣泛使用的神經網絡正則化技術&#xff0c;對每一層的輸入進行標準化&#xff0c;進行縮放和平移&#xff0c;目的是加速訓練&#xff0c;提高模型穩定性和泛化能力&#xff0c;通常在全連接層或是卷積層之和&#xff0c;激活函數之前使用核心思想對每一批…

【數據可視化-67】基于pyecharts的航空安全深度剖析:墜毀航班數據集可視化分析

&#x1f9d1; 博主簡介&#xff1a;曾任某智慧城市類企業算法總監&#xff0c;目前在美國市場的物流公司從事高級算法工程師一職&#xff0c;深耕人工智能領域&#xff0c;精通python數據挖掘、可視化、機器學習等&#xff0c;發表過AI相關的專利并多次在AI類比賽中獲獎。CSDN…

【科研繪圖系列】R語言繪制分組箱線圖

文章目錄 介紹 加載R包 數據下載 導入數據 畫圖1 畫圖2 合并圖 系統信息 參考 介紹 【科研繪圖系列】R語言繪制分組箱線圖 加載R包 library(ggplot2) library(patchwork)rm(list = ls()) options(stringsAsFactors = F)

基于Android的旅游計劃App

項目介紹系統打開進入登錄頁面&#xff0c;如果沒有注冊過賬號&#xff0c;點擊注冊按鈕輸入賬號、密碼、郵箱即可注冊&#xff0c;注冊后可登錄進入系統&#xff0c;系統分為首頁、預訂、我的三大模塊&#xff0c;下面具體詳細說說三大模塊功能說明。1.首頁顯示旅游備忘或旅游…

【LeetCode 2163. 刪除元素后和的最小差值】解析

目錄LeetCode中國站原文原始題目題目描述示例 1&#xff1a;示例 2&#xff1a;提示&#xff1a;講解分割線的藝術&#xff1a;前后綴分解與優先隊列的完美邂逅第一部分&#xff1a;算法思想 —— “分割線”與前后綴分解1. 想象一條看不見的“分割線”2. 前后綴分解&#xff1…

控制鼠標和鍵盤

控制鼠標和鍵盤的Python庫Python中有多個庫可以用于控制鼠標和鍵盤&#xff0c;常用的包括pyautogui、pynput、keyboard和mouse等。這些庫提供了模擬用戶輸入的功能&#xff0c;適用于自動化測試、GUI操作等場景。使用pyautogui控制鼠標pyautogui是一個跨平臺的庫&#xff0c;支…

基于按鍵開源MultiButton框架深入理解代碼框架(二)(指針的深入理解與應用)

文章目錄2、針對該開源框架理解3、分析代碼3.1 再談指針、數組、數組指針3.2 繼續分析源碼2、針對該開源框架理解 在編寫按鍵模塊的框架中&#xff0c;一定要先梳理按鍵相關的結構體、枚舉等變量。這些數據是判斷按鍵按下、狀態跳轉、以及綁定按鍵事件的核心。 這一部分定義是…

web前端渡一大師課 CSS屬性計算過程

你是否了解CSS 的屬性計算過程呢? <body> <h1>這是一個h1標題</h1> </body> 目前我們沒有設置改h1的任何樣式,但是卻能看到改h1有一定的默認樣式,例如有默認的字體大小,默認的顏色 那么問題來了,我們這個h1元素上面除了有默認字體大小,默認顏色等…

Redis高頻面試題:利用I/O多路復用實現高并發

Redis 通過 I/O 多路復用&#xff08;I/O Multiplexing&#xff09;技術實現高并發&#xff0c;這是其單線程模型能夠高效處理大量客戶端連接的關鍵。以下是通俗易懂的解釋&#xff0c;結合 Redis 的工作原理&#xff0c;詳細說明其實現過程。 1. 什么是 I/O 多路復用&#xff…

爬蟲小知識(二)網頁進行交互

一、提交信息到網頁 1、模塊核心邏輯 “提交信息到網頁” 是網絡交互關鍵環節&#xff0c;借助 requests 庫的 post() 函數&#xff0c;能模擬瀏覽器向網頁發數據&#xff08;如表單、文件 &#xff09;&#xff0c;實現信息上傳&#xff0c;讓我們能與網頁背后的服務器 “溝通…

WPF學習(五)

文章目錄一、FileStream和StreamWriter理解1.1、具體關系解析1.2、類比理解1.3、總結1.4、示例代碼1.5、 WriteLine()和 Write&#xff08;&#xff09;的區別1.6、 StreamWriter.Close的作用二、一、FileStream和StreamWriter理解 在 C# 中&#xff0c;StreamWriter 和 FileS…

ctf.show-web習題-web2-最簡單的sql注入-flag獲取詳解、總結

解題思路打開靶場既然提示是最簡單的sql注入了&#xff0c;那么直接嘗試永真登錄1 or 11#這里閉合就是簡單的單引號可以看到沒登錄成功&#xff0c;但是有回顯&#xff1a;歡迎你&#xff0c;ctfshowsql注入最喜歡的就是回顯了&#xff01;這題的思路就是靠這個回顯&#xff0c…