Rustのマルチスレッドプログラミング

Rustのマルチスレッドプログラミング

スレッドモジュールを使用する

Rust 標準ライブラリは、直接参照して使用できるスレッド関連のサポートを提供します。

use std::thread;

スレッドを作成する

spawn メソッドを使用してスレッドを作成します。のように:

use std::thread;/* 引用线程模块 */
use std::time::Duration;
fn main() {
    
    
    std::thread::spawn(thread_function);
    loop {
    
    
        thread::sleep(Duration::from_secs(1));/* sleep 1s */
        println!("main thread running..");
    }
}

fn thread_function(){
    
    
    loop {
    
    
        thread::sleep(Duration::from_secs(1));/* sleep 1s */
        println!("demo thread running..");
    }
}

スレッドは作成後に自動的に実行されます。

boys@server:~/rust_study/demo$ cargo run
   Compiling demo v0.1.0 (/home/boys/rust_study/demo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/demo`
main thread running..
demo thread running..
main thread running..
demo thread running..
main thread running..
demo thread running..
^C
boys@server:~/rust_study/demo$ 

パラメータを渡すスレッド

デフォルトの生成メソッドはパラメータを渡すことができません。スレッドにパラメータを渡したい場合は、spawn のパラメータとして匿名関数を使用する必要があります (クロージャとも呼ばれます)。

クロージャ(無名関数)

クロージャの基本構文:

|param1, param2, ...| {
    
    
    // 函数体
}

クロージャを使用する利点は、関数の外で変数をキャプチャして関数本体に渡すことができることです。

変数の捉え方により、次のように分けられます。

  • 価値の獲得
  • 通常のリファレンスキャプチャ
  • 可変参照キャプチャ

価値の獲得

値がキャプチャされる方法は、変数の所有権がクロージャに転送され、外部から使用できなくなることです。のように:

fn main(){
    
    
    let str = String::from("hello");
    let closure_print_string = move ||{
    
    /* move表明函数体内捕获的变量使用值捕获方式 */
        println!("number = {}", str);/* 使用值捕获方式捕获外部的str */
    };
    closure_print_string();
    println!("test for str: {}", str);/* 值捕获方式,str的所有权已经被转移到闭包内,这里无法再使用 */
}

不変参照のキャプチャ

クロージャは、キャプチャされた変数の型を自動的に認識します。関数本体でキャプチャした変数の種類からキャプチャ方法を確認します。

不変変数は不変参照方式に従ってキャプチャおよび使用されるため、元の変数値を変更することはできず、変数値にアクセスすることのみが可能です。のように:

fn main(){
    
    
    let str = String::from("hello");/* str是不可变 */
    let closure = ||{
    
    
        println!("str: {}", str);
        // str.push_str("world");/* 不可变的变量按不可变引用方式捕获,此处修改了变量值会报错 */
    };
    closure();
    println!("test for str: {}", str);
}

可変参照キャプチャ

外部変数を可変変数に変えることにより、変数の値をクロージャー関数本体内で変更できます。

fn main(){
    
    
    let mut str = String::from("hello");/* str是不可变 */
    let mut closure = ||{
    
    
        println!("before push, str: {}", str);
        str.push_str(" world");/* 不可变的变量按不可变引用方式捕获,此处修改了变量值会报错 */
    };
    closure();
    println!("test for str: {}", str);
}

同時に、クロージャのタイプも可変として定義する必要があります。外部変数変数がキャプチャされる限り、それらは mut として定義されます。のように:

fn main(){
    
    
    let num = 123;
    let mut str = String::from("hello");/* str是不可变 */
    let mut closure = ||{
    
    
        println!("num: {}", num);/* num不可变 */
        println!("before push, str: {}", str);
        str.push_str(" world");/* 不可变的变量按不可变引用方式捕获,此处修改了变量值会报错 */
    };
    closure();
    println!("test for str: {}", str);
}

パラメータを渡すスレッド クロージャ

これで、クロージャ メソッドを使用して外部パラメータをスレッドに渡すことができます。

のように:

fn main(){
    
    
    let word = "what are words";
    let thread_handle = std::thread::spawn(move ||{
    
    
        println!("just print 'word': {}", word);
    });
    thread_handle.join().unwrap();
}

ここで値キャプチャ メソッドを使用して、文字列参照変数 str の所有権をクロージャに転送する必要があります。コンパイル時にクロージャと現在の関数の宣言サイクルがチェックされるため、クロージャが別のスレッドに渡されるため、現在の関数の終了後にクロージャが実行される可能性があることがわかります。

closure may outlive the current function, but it borrows `word`, which is owned by the current function

パラメータをよりエレガントに渡す

外部パラメータはクロージャを通じて別のスレッドに渡すことができますが、クロージャは通常、比較的単純な関数を実装するために使用されます。スレッドの場合は、より複雑な関数が存在するため、より洗練された方法は、関数を使用してスレッドをカプセル化し、クロージャを使用してパラメータを渡してスレッドを作成することです。のように:

use std::time::Duration;
struct ThreadParam{
    
    
    thread_name: String
}
fn demo_thread(param: ThreadParam){
    
    
    for _ in 0..3{
    
    /* 使用下划线作为迭代变量,避免编译警告 */
        std::thread::sleep(Duration::from_secs(1));
        println!("thread's name: {}", param.thread_name);
    }
}
fn main(){
    
    
    let thread_arguments = ThreadParam{
    
    
        thread_name: String::from("demo_thread")
    };
    let thread_handle = std::thread::spawn(move ||{
    
    
        demo_thread(thread_arguments);
    });
    thread_handle.join().unwrap();/* 回收线程,防止主线程退出看不到线程效果 */
}

スレッドをリサイクルする

join メソッドを使用して、スレッド リソースをリサイクルします。のように:

use std::thread;/* 引用线程模块 */
use std::time::Duration;
fn main() {
    
    
    let thread_handle = std::thread::spawn(thread_function);
    thread_handle.join().unwrap();
    println!("thread exit.");
}

fn thread_function(){
    
    
    let mut count = 1;
    loop {
    
    
        thread::sleep(Duration::from_secs(1));/* sleep 1s */
        println!("demo thread run {} time..", count);
        count = count + 1;
        if count > 3 {
    
    
            break;
        }
    }
}
boys@server:~/rust_study/demo$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/demo`
demo thread run 1 time..
demo thread run 2 time..
demo thread run 3 time..
thread exit.

スレッドの同期と通信

Rust は、スレッドの同期と通信のためのいくつかの組み込みメカニズムを提供します。

  • チャネル
  • ミューテックス(ミューテックスロック)
  • 原子の種類
  • 条件変数
  • 原子参照計数 (Arc)
  • バリア

チャンネルチャンネル

チャネルは、マルチプロデューサー、シングルコンシューマーのシナリオでのみ使用できます。モジュールはstd::sync::mpscmpsc です。これは、「Multi-Producer, Single-Consumer」、つまり、マルチプロデューサーとシングルコンシューマーの略です。

use std::sync::mpsc;

使用例:

fn main(){
    
    
    let (sender, receiver) = std::sync::mpsc::channel();
    let sender1 = sender.clone();/* 通过克隆2个sender传递到线程中 */
    let sender2 = sender.clone();

    let sender1_thread_handle = std::thread::spawn(move||{
    
    
        std::thread::sleep(std::time::Duration::from_secs(1));
        sender1.send("hello").unwrap();
    });
    let sender2_thread_handle = std::thread::spawn(move||{
    
    
        std::thread::sleep(std::time::Duration::from_secs(2));
        sender2.send("bye").unwrap();
    });
    let mut is_connected = false;
    loop {
    
    
        let recv_data = receiver.recv().unwrap();
        if recv_data == "hello"{
    
    
            println!("recv 'hello', connected");
            is_connected = true;
        }
        if is_connected == true{
    
    
            if recv_data == "bye"{
    
    
                println!("recv 'bye', disconnected");
                break;
            }
        }
    }
    sender1_thread_handle.join().unwrap();
    sender2_thread_handle.join().unwrap();
}

ミューテックス ミューテックス ロック

インポートモジュール:

use std::sync::Mutex;

RustのMutexはジェネリック型です。保護する共有データの種類std::sync::Mutex<T>として定義されます。Tデータ型は初期化中に自動的に決定されます。

以下は、Mutex を使用して整数変数への相互排他的アクセスを実行する例です。

use std::sync::{
    
    Mutex, Arc};
use std::thread;

fn main() {
    
    
    let data = Arc::new(Mutex::new(42));

    let thread1_data = Arc::clone(&data);
    let handle1 = thread::spawn(move || {
    
    
        // 使用 thread1_data 进行操作
        let mut value = thread1_data.lock().unwrap();
        *value += 1;
        println!("thread1 Value: {}", *value);
    });

    let thread2_data = Arc::clone(&data);
    let handle2 = thread::spawn(move || {
    
    
        // 使用 thread2_data 进行操作
        let value = thread2_data.lock().unwrap();
        println!("thread2 Value: {}", *value);
    });

    handle1.join().unwrap();
    handle2.join().unwrap();
}

ここでは、Arc アトミック参照カウント タイプを使用して、複数のスレッドがミューテックス ロックの所有権を共有することを実現します。

バリアフェンス

Rust では、Barrier型を使用してスレッド フェンスを実装できます。Barrierスレッドのグループがすべてある点に到達し、同時に実行を継続できるようにします。

例:

use std::sync::{
    
    Arc, Barrier};
use std::thread;

fn main() {
    
    
    let barrier = Arc::new(Barrier::new(3)); // 创建一个包含3个参与者的栅栏

    for i in 0..3 {
    
    /* 创建3个线程 */
        /* 使用Arc原子引用计数将barrier共享给所有线程 */
        let barrier_clone = Arc::clone(&barrier);

        thread::spawn(move || {
    
    
            println!("Thread {} before barrier", i);
        
            barrier_clone.wait(); // 所有线程都到达此处后会同时继续执行
        
            println!("Thread {} after barrier", i);
        });
    }

    thread::sleep(std::time::Duration::from_secs(2)); // 等待足够的时间以确保所有线程完成
  
    println!("Main thread");
}

アトミックタイプ アトミックタイプ

Rust で一般的に使用されるアトミック タイプは次のとおりです。

  1. AtomicBool アトミック ブール型
  2. AtomicI32 アトミック整数型
  3. AtomicPtr アトミック ポインタ型

アトミック タイプは分割できないタイプであり、アトミック タイプ変数に対する操作は分割できないため、競合状態を回避するためにマルチスレッド同時実行シナリオでよく使用されます。

簡単な使用例:

use std::sync::atomic::{
    
    AtomicI32, Ordering};
use std::sync::Arc;
use std::thread;

fn main() {
    
    
    let atomic_counter = Arc::new(AtomicI32::new(0));
    let mut handles = vec![];/* 使用vec保存线程句柄,回收资源使用 */

    for _ in 0..5 {
    
    
        let counter = Arc::clone(&atomic_counter);
        let handle = thread::spawn(move || {
    
    
            for _ in 0..1_0 {
    
    
                counter.fetch_add(1, Ordering::SeqCst);
            }
        });
        handles.push(handle);
    }

    for handle in handles {
    
    
        handle.join().unwrap();
    }

    println!("Final value: {}", atomic_counter.load(Ordering::SeqCst));
}

アトミック変数を 10 回インクリメントするために 5 つのスレッドが作成され、出力結果は 50 になります。アトミック変数が使用されず、相互排他保護が提供されない場合、得られる結果は不明です。

boys@server:~/rust_study/demo$ cargo run 
   Compiling demo v0.1.0 (/home/boys/rust_study/demo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/demo`
Final value: 50

おすすめ

転載: blog.csdn.net/qq_41790078/article/details/132568479