スレッド

スレッドも使える。

  1. スレッドを生成する
  2. スレッドの終了を待機する
  3. スレッドに変数を渡す
スレッドを生成する

スレッドを使用するには、use std::threadでスレッドの使用を宣言し、スレッドで実行したい処理をクロージャでspawnに渡せば新しいスレッドが生成される。

use std::thread;
use std::time::SystemTime;

fn main() {
    thread::spawn(|| {
        println!("生成したスレッド {:?}", SystemTime::now());
    });
    println!("メインスレッド {:?}", SystemTime::now());
}

しかし、これを実行すると大抵は以下のようにメインスレッドのprintlnしか実行されないだろう。

メインスレッド SystemTime { tv_sec: 1610676356, tv_nsec: 895161270 }

環境やタイミングによっては、以下のように生成したスレッドのprintlnも実行される場合もあるが、確率としては低いだろう。

メインスレッド SystemTime { tv_sec: 1610676361, tv_nsec: 485496980 }
生成したスレッド SystemTime { tv_sec: 1610676361, tv_nsec: 485523862 }

これは、新しいスレッドをspawnで生成して実行する前にメインスレッドが終了してしまい、生成したスレッドが生成されない、あるいは終了させられてしまうからだ。

スレッドの終了を待機する

spawnで生成したスレッドの処理も確実に実行させるには、スレッドからJoinHandleを取得してjoin、すなわちスレッドの終了を待機すれば良い。

fn main() {
    // JoinHandleを得る
    let handle = thread::spawn(|| {
        println!("生成したスレッド {:?}", SystemTime::now());
    });
    println!("メインスレッド {:?}", SystemTime::now());

    // スレッドの終了を待機
    handle.join().unwrap();
}

これで確実に両方のprintlnが実行されるだろう。また、スレッドをjoinするとスレッドから値を渡すことができるので、スレッドの計算結果をメインスレッドに戻す場合等に使えるだろう。

fn main() {
    // JoinHandleを得る
    let handle = thread::spawn(|| {
        println!("生成したスレッド {:?}", SystemTime::now());
        // スレッドから値を渡す
        SystemTime::now()
    });
    println!("メインスレッド {:?}", SystemTime::now());

    // スレッドの終了を待機
    let ret = handle.join().unwrap();
    println!("{:?}", ret);
}
メインスレッド SystemTime { tv_sec: 1610676723, tv_nsec: 820622100 }
生成したスレッド SystemTime { tv_sec: 1610676723, tv_nsec: 820689400 }
SystemTime { tv_sec: 1610676723, tv_nsec: 822163600 }
スレッドに変数を渡す

スレッドに変数を渡すこともできる。と言っても、クロージャの引数として渡せるわけではなく、生成元のスコープ内の変数をキャプチャして、新たに生成されるスレッドで使用できるというものである。やり方はspawnするクロージャにmoveキーワードを付加するだけだ。

この例ではスレッドのクロージャ内でnum変数を使用しているので、num変数がクロージャにキャプチャされる。

fn main() {
    let mut num = 0;
    // クロージャにmoveキーワードを付加する
    let handle = thread::spawn(move || {
        // メインスレッドのnumが使用できる
        num += 1;
        println!("生成したスレッド num: {}", num);
    });

    // スレッドの終了を待機
    handle.join().unwrap();
    // 整数型の場合は値のコピーがスレッドに渡されるだけなのでnumは0のまま
    println!("メインスレッド num: {}", num);
}
生成したスレッド num: 1
メインスレッド num: 0

因みに、上記のコードでmoveキーワードがなかったとすると、コンパイル時に以下のようなエラーになりmoveキーワードをクロージャに付加するように言われる。

error[E0373]: closure may outlive the current function, but it borrows `num`, which is owned by the current function
  --> src/main.rs:36:32
   |
36 |     let handle = thread::spawn(|| {
   |                                ^^ may outlive borrowed value `num`
37 |         // メインスレッドのnumが使用できる
38 |         num += 1;
   |         --- `num` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src/main.rs:36:18
   |
36 |       let handle = thread::spawn(|| {
   |  __________________^
37 | |         // メインスレッドのnumが使用できる
38 | |         num += 1;
39 | |         println!("生成したスレッド num: {}", num);
40 | |     });
   | |______^
help: to force the closure to take ownership of `num` (and any other referenced variables), use the `move` keyword
   |
36 |     let handle = thread::spawn(move || {

ただし、オブジェクトの場合は注意が必要になる。オブジェクトは、生成されるスレッドに変数を渡してしまうと、以後それを生成元スコープでは使用できなくなってしまう。

fn main() {
    let mut s = "Rust".to_string();
    let handle = thread::spawn(move || {
        // メインスレッドのsが使用できる
        s += "は楽しい!";
        println!("生成したスレッド s: {}", s);
    });

    // スレッドの終了を待機
    handle.join().unwrap();
    // オブジェクトの場合は所有権がスレッドに移るのでsは使用できない
    //println!("メインスレッド s: {}", s);
}
生成したスレッド s: Rustは楽しい!

spawnのクロージャに変数をmoveすると、整数型の場合は値のコピーが渡されるだけだが、String等のオブジェクトは所有権が移ってしまう。それ故、スレッドを生成したスコープではmoveした変数は使用できなくなる。

error[E0382]: borrow of moved value: `s`
  --> src/main.rs:53:31
   |
43 |     let mut s = "Rust".to_string();
   |         ----- move occurs because `s` has type `String`, which does not implement the `Copy` trait
44 |     let handle = thread::spawn(move || {
   |                                ------- value moved into closure here
45 |         // メインスレッドのsが使用できる
46 |         s += "は楽しい!";
   |         - variable moved due to use in closure
...
53 |     println!("メインスレッド s: {}", s);
   |                                      ^ value borrowed here after move
(2021/01/15)

新着情報
【オープンソースソフトウェア環境構築】Apple silicon Macで開発環境を構築
【Rust Tips】Actix webでJSONをPOSTする
【Rust Tips】コマンドライン引数を取得する

Copyright© 2004-2021 モバイル開発系(K) All rights reserved.
[Home]