English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Rust並行プログラミング

安全かつ効率的な並行処理は、Rustが誕生した目的の1つであり、主にサーバーの高負荷耐久性を解決するものです。

並行(concurrent)の概念は、プログラムの異なる部分が独立して実行されることを意味し、これは並行(parallel)の概念と混同しやすいですが、並行は「同時に実行」と強調します。

並行は通常、並行に進みます。

この章では、並行に関連するプログラミング概念と詳細について説明します。

スレッド

スレッドはプログラム内で独立に実行される一部です。

スレッドとプロセスの違いは、スレッドがプログラム内の概念であり、プログラムは通常プロセス内で実行されることです。

オペレーティングシステムの環境では、プロセスは通常交替でスケジューリングされて実行され、スレッドはプロセス内でプログラムによってスケジューリングされます。

スレッドの並行が並行状況を引き起こす可能性があるため、死锁や遅延エラーが並行メカニズムを持つプログラムによく発生します。

これらの問題を解決するために、多くの他の言語(例えば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!("受け取った: {}", received);
}

実行結果:

受け取った: hi

サブスレッドがメインスレッドの送信者txを取得し、そのsendメソッドを呼び出して文字列を送信し、その後メインスレッドが対応する受信者rxを通じて受け取ります。