學習Rust 併發程式設計

大雄45發表於2020-12-31
導讀 Rust 語言是一種高效、可靠的通用高階語言。其高效不僅限於開發效率,它的執行效率也是令人稱讚的,是一種少有的兼顧開發效率和執行效率的語言。

學習Rust 併發程式設計學習Rust 併發程式設計
安全高效的處理併發是 Rust 誕生的目的之一,主要解決的是伺服器高負載承受能力。
併發(concurrent)的概念是隻程式不同的部分獨立執行,這與並行(parallel)的概念容易混淆,並行強調的是"同時執行"。
併發往往會造成並行。

執行緒

執行緒(thread)是一個程式中獨立執行的一個部分。
執行緒不同於程式(process)的地方是執行緒是程式以內的概念,程式往往是在一個程式中執行的。
在有作業系統的環境中程式往往被交替地排程得以執行,執行緒則在程式以內由程式進行排程。
由於執行緒併發很有可能出現並行的情況,所以在並行中可能遇到的死鎖、延宕錯誤常出現於含有併發機制的程式。
為了解決這些問題,很多其它語言(如 Java、C#)採用特殊的執行時(runtime)軟體來協調資源,但這樣無疑極大地降低了程式的執行效率。
C/C++ 語言在作業系統的最底層也支援多執行緒,且語言本身以及其編譯器不具備偵察和避免並行錯誤的能力,這對於開發者來說壓力很大,開發者需要花費大量的精力避免發生錯誤。
Rust 不依靠執行時環境,這一點像 C/C++ 一樣。
但 Rust 在語言本身就設計了包括所有權機制在內的手段來儘可能地把最常見的錯誤消滅在編譯階段,這一點其他語言不具備。
但這不意味著我們程式設計的時候可以不小心,迄今為止由於併發造成的問題還沒有在公共範圍內得到完全解決,仍有可能出現錯誤,併發程式設計時要儘量小心!
Rust 中透過 std::thread::spawn 函式建立新程式:

例項

use std::thread;
use std::time::Duration;
fn spawn_function() {
    for i in 0..5 {
        println!("spawned thread print {}", i);
        thread::sleep(Duration::from_millis(1));
    }
}
fn main() {
    thread::spawn(spawn_function);
    for i in 0..3 {
        println!("main thread print {}", i);
        thread::sleep(Duration::from_millis(1));
    }
}

執行結果:

main thread print 0
spawned thread print 0
main thread print 1
spawned thread print 1
main thread print 2
spawned thread print 2

這個結果在某些情況下順序有可能變化,但總體上是這樣列印出來的。

此程式有一個子執行緒,目的是列印 5 行文字,主執行緒列印三行文字,但很顯然隨著主執行緒的結束,spawn 執行緒也隨之結束了,並沒有完成所有列印。

std::thread::spawn 函式的引數是一個無參函式,但上述寫法不是推薦的寫法,我們可以使用閉包(closures)來傳遞函式作為引數:

例項

use std::thread;
use std::time::Duration;
fn main() {
    thread::spawn(|| {
        for i in 0..5 {
            println!("spawned thread print {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
    for i in 0..3 {
        println!("main thread print {}", i);
        thread::sleep(Duration::from_millis(1));
    }
}

閉包是可以儲存進變數或作為引數傳遞給其他函式的匿名函式。閉包相當於 Rust 中的 Lambda 表示式,格式如下:

|引數1, 引數2, ...| -> 返回值型別 {
    // 函式體
}

例如:

例項

fn main() {
    let inc = |num: i32| -> i32 {
        num + 1
    };
    println!("inc(5) = {}", inc(5));
}

執行結果:

inc(5) = 6

閉包可以省略型別宣告使用 Rust 自動型別判斷機制:

例項

fn main() {
    let inc = |num| {
        num + 1
    };
    println!("inc(5) = {}", inc(5));
}

結果沒有變化。

join 方法

例項

use std::thread;
use std::time::Duration;
fn main() {
    let handle = thread::spawn(|| {
        for i in 0..5 {
            println!("spawned thread print {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
    for i in 0..3 {
        println!("main thread print {}", i);
        thread::sleep(Duration::from_millis(1));
    }
    handle.join().unwrap();
}

執行結果:

main thread print 0 
spawned thread print 0 
spawned thread print 1 
main thread print 1 
spawned thread print 2 
main thread print 2 
spawned thread print 3 
spawned thread print 4

join 方法可以使子執行緒執行結束後再停止執行程式。

move 強制所有權遷移

這是一個經常遇到的情況:

例項

use std::thread;
fn main() {
    let s = "hello";
   
    let handle = thread::spawn(|| {
        println!("{}", s);
    });
    handle.join().unwrap();
}

在子執行緒中嘗試使用當前函式的資源,這一定是錯誤的!因為所有權機制禁止這種危險情況的產生,它將破壞所有權機制銷燬資源的一定性。我們可以使用閉包的 move 關鍵字來處理:

例項

use std::thread;
fn main() {
    let s = "hello";
   
    let handle = thread::spawn(move || {
        println!("{}", s);
    });
    handle.join().unwrap();
}
訊息傳遞

Rust 中一個實現訊息傳遞併發的主要工具是通道(channel),通道有兩部分組成,一個傳送者(transmitter)和一個接收者(receiver)。

std::sync::mpsc 包含了訊息傳遞的方法:

例項

use std::thread;
use std::sync::mpsc;
fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
    });
    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

執行結果:

Got: hi

子執行緒獲得了主執行緒的傳送者 tx,並呼叫了它的 send 方法傳送了一個字串,然後主執行緒就透過對應的接收者 rx 接收到了。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2746746/,如需轉載,請註明出處,否則將追究法律責任。

相關文章