偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

在 Rust 編程中使用多線程

開發(fā)
編程語言有一些不同的方法來實現(xiàn)線程,而且很多操作系統(tǒng)提供了創(chuàng)建新線程的 API。Rust 標準庫使用 1:1 線程實現(xiàn),這代表程序的每一個語言級線程使用一個系統(tǒng)線程。

1. Rust線程實現(xiàn)理念

在大部分現(xiàn)代操作系統(tǒng)中,已執(zhí)行程序的代碼在一個 進程(process)中運行,操作系統(tǒng)則會負責管理多個進程。在程序內部,也可以擁有多個同時運行的獨立部分。這些運行這些獨立部分的功能被稱為 線程(threads)。例如,web 服務器可以有多個線程以便可以同時響應多個請求。

將程序中的計算拆分進多個線程可以改善性能,因為程序可以同時進行多個任務,不過這也會增加復雜性。因為線程是同時運行的,所以無法預先保證不同線程中的代碼的執(zhí)行順序。這會導致諸如此類的問題:

  • 競態(tài)條件(Race conditions),多個線程以不一致的順序訪問數(shù)據(jù)或資源。
  • 死鎖(Deadlocks),兩個線程相互等待對方,這會阻止兩者繼續(xù)運行。
  • 只會發(fā)生在特定情況且難以穩(wěn)定重現(xiàn)和修復的 bug。

Rust 嘗試減輕使用線程的負面影響。不過在多線程上下文中編程仍需格外小心,同時其所要求的代碼結構也不同于運行于單線程的程序。

編程語言有一些不同的方法來實現(xiàn)線程,而且很多操作系統(tǒng)提供了創(chuàng)建新線程的 API。Rust 標準庫使用 1:1 線程實現(xiàn),這代表程序的每一個語言級線程使用一個系統(tǒng)線程。

2.使用spawn創(chuàng)建新線程

為了創(chuàng)建一個新線程,需要調用 thread::spawn 函數(shù)并傳遞一個閉包, 并在其中包含希望在新線程運行的代碼??聪旅娴睦?

use std::thread;
use std::time::Duration;

fn main() {
    thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}

注意當 Rust 程序的主線程結束時,新線程也會結束,而不管其是否執(zhí)行完畢。這個程序的輸出可能每次都略有不同,不過它大體上看起來像這樣:

thread::sleep 調用強制線程停止執(zhí)行一小段時間,這會允許其他不同的線程運行。這些線程可能會輪流運行,不過并不保證如此:這依賴操作系統(tǒng)如何調度線程。在這里,主線程首先打印,即便新創(chuàng)建線程的打印語句位于程序的開頭,甚至即便我們告訴新建的線程打印直到 i 等于 9,它在主線程結束之前也只打印到了 5。

如果運行代碼只看到了主線程的輸出,或沒有出現(xiàn)重疊打印的現(xiàn)象,嘗試增大區(qū)間 (變量 i 的范圍) 來增加操作系統(tǒng)切換線程的機會。

3.使用join等待所有線程結束

由于主線程結束,上面演示的代碼大部分時候不光會提早結束新建線程,因為無法保證線程運行的順序,甚至不能實際保證新建線程會被執(zhí)行!

可以通過將 thread::spawn 的返回值儲存在變量中來修復新建線程部分沒有執(zhí)行或者完全沒有執(zhí)行的問題。thread::spawn 的返回值類型是 JoinHandle。JoinHandle 是一個擁有所有權的值,當對其調用 join 方法時,它會等待其線程結束。

看下面的示例代碼:

use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

    handle.join().unwrap();
}

通過調用 handle 的 join 會阻塞當前線程直到 handle 所代表的線程結束。阻塞(Blocking)線程意味著阻止該線程執(zhí)行工作或退出。因為我們將 join 調用放在了主線程的 for 循環(huán)之后,編譯這段代碼后運行結果如下:

這兩個線程仍然會交替執(zhí)行,不過主線程會由于 handle.join() 調用會等待直到新建線程執(zhí)行完畢。

不過如果將 handle.join() 移動到 main 中 for 循環(huán)之前會發(fā)生什么呢,看下面的代碼:

use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    handle.join().unwrap();

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}

代碼編譯執(zhí)行后結果如下:

主線程會等待直到新建線程執(zhí)行完畢之后才開始執(zhí)行 for 循環(huán),所以輸出將不會交替出現(xiàn)。

因此,將join放在代碼的不同地方, 將會影響線程是否同時執(zhí)行。

4.將move閉包與線程一起使用

move 關鍵字經(jīng)常用于傳遞給 thread::spawn 的閉包,因為閉包會獲取從環(huán)境中取得的值的所有權,因此會將這些值的所有權從一個線程傳送到另一個線程。

為了在新建線程中使用來自于主線程的數(shù)據(jù),需要新建線程的閉包獲取它需要的值, 下面的代碼展示了一個嘗試在主線程中創(chuàng)建一個 vector 并用于新建線程的例子,不過這么寫還不能工作, 代碼如下:

use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(|| {
        println!("Here's a vector: {:?}", v);
    });

    handle.join().unwrap();
}

閉包使用了 v,所以閉包會捕獲 v 并使其成為閉包環(huán)境的一部分。因為 thread::spawn 在一個新線程中運行這個閉包,所以可以在新線程中訪問 v。然而當編譯這個例子時,會得到如下錯誤:

Rust 會 推斷 如何捕獲 v,因為 println! 只需要 v 的引用,閉包嘗試借用 v。然而這有一個問題:Rust 不知道這個新建線程會執(zhí)行多久,所以無法知曉對 v 的引用是否一直有效。

看一段比較極端情況的代碼:

use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(|| {
        println!("Here's a vector: {:?}", v);
    });

    drop(v); // 壞事兒了!

    handle.join().unwrap();
}

如果 Rust 允許這段代碼運行,則新建線程則可能會立刻被轉移到后臺并完全沒有機會運行。新建線程內部有一個 v 的引用,不過主線程立刻就使用drop丟棄了v。接著當新建線程開始執(zhí)行,v 已不再有效,所以其引用也是無效的。

為了修復上面的編譯錯誤, 我們可以根據(jù)編譯器給予我們的help嘗試修正一下,如圖:

通過在閉包之前增加 move 關鍵字,強制閉包獲取其使用的值的所有權,而不是任由 Rust 推斷它應該借用值。

修正后的代碼如下:

use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });

    handle.join().unwrap();
}

編譯運行試一下:

看起來沒問題,那么以這個成功的經(jīng)驗, 修改那段極端情況的代碼如下:

use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });

    drop(v); // 壞事兒了!

    handle.join().unwrap();
}

再次編譯一下看看結果如何:

Rust編譯器依然沒有放行, 這個修復行不通。

如果為閉包增加 move,將會把 v 移動進閉包的環(huán)境中, 因此將不能在主線程中對其調用 drop 了, Rust 的所有權規(guī)則又一次幫助我們杜絕了隱患。因為 Rust 是保守的并只會為線程借用 v,這意味著主線程理論上可能使新建線程的引用無效。通過告訴 Rust 將 v 的所有權移動到新建線程,我們向 Rust 保證主線程不會再使用 v。如果對其作出同樣的move修改, 那么當在主線程中使用 v 時就會違反所有權規(guī)則。move 關鍵字覆蓋了 Rust 默認保守的借用,但它不允許我們違反所有權規(guī)則。

責任編輯:趙寧寧 來源: 二進制空間安全
相關推薦

2024-01-09 09:27:57

Rust編程泛型

2011-08-31 16:30:19

Lua多線程

2023-06-15 17:00:11

Rust循環(huán)

2024-03-26 09:25:35

RustSerde重命名

2024-03-06 08:40:16

ReactJavascriptUI交互

2023-05-04 07:33:39

Rust變量常量

2021-03-22 08:45:30

異步編程Java

2013-07-16 10:12:14

iOS多線程多線程概念多線程入門

2009-10-13 09:56:13

.NET多線程程序

2023-06-13 13:39:00

多線程異步編程

2024-09-06 11:34:15

RustAI語言

2009-03-12 10:52:43

Java線程多線程

2013-06-07 16:30:08

iOS多線程iOS開發(fā)NSThread

2011-12-08 10:24:53

JavaNIO

2024-02-07 11:44:20

NestJSRxJS異步編程

2024-04-07 00:00:10

Rust枚舉C代碼

2023-04-02 17:53:10

多線程編程自測

2009-12-08 12:14:43

2023-06-07 13:49:00

多線程編程C#

2023-06-05 07:56:10

線程分配處理器
點贊
收藏

51CTO技術棧公眾號