RustのThread(スレッド)で並行処理をする方法

668, 2023-05-25

目次

RustのThreadで並行処理

Rustで並行処理をしたい場合に選択肢の1つに上がるのがスレッドです。
スレッドとはプログラムで並行処理を行うための仕組みです。
スレッドを起動し、関数を実行させ、それをメインスレッドから切り離して並行的に実行します。

この記事ではRustのスレッドについて解説します。

thread::spawn()でスレッドを起動する

Rustでスレッドを起動するにはstd::thread::spawn()を使います。
このspawn()に関数を渡すとスレッドが起動してその関数が平行的に実行されるようになります。

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

fn main() {
    thread::spawn(|| {
        for i in 0..10 {
            println!("sub {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 0..10 {
        println!("main {}", i);
        thread::sleep(Duration::from_millis(1));
    }
}

上記のコードの出力は以下になります。

main 0
sub 0
sub 1
main 1
main 2
sub 2
sub 3
main 3
main 4
sub 4
main 5
sub 5
main 6
sub 6
main 7
sub 7
sub 8
main 8
sub 9
main 9

上記の出力を見ると2つのfor文が並行的に入り乱れているのがわかりますね。

私たちが普段いる世界はメインスレッドの世界です。
プログラムが起動するとLinuxではプロセスが作成され、そのプロセスの中にメインスレッドが1つ作られます。
そしてメインスレッド内で私たちはfor文を回したりprintln!()を実行したりしています。

thread::spawn()を実行すると私たちのいるメインスレッドの世界から新しく別のスレッドが作られます。
そしてそのスレッドがメインスレッドが切り離されて同じプロセスの中で並行的に動くようになります。
切り離されていると言っても別のスレッドからメインスレッドの変数にアクセスするなどは可能です。

join()でスレッドの終了を待機する

thread::spawn()でスポーンしたスレッドはメインスレッドが終了すると破棄されてしまいます。
つまりスポーンしたスレッドの終了を待つ必要があります。
そういう時はjoin()を使ってスレッドの終了を待機します。

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        for i in 0..10 {
            println!("sub {}", i);
        }
    });

    println!("main");

    handle.join().unwrap();
}

上記のコードの出力は以下になります。

main
sub 0
sub 1
sub 2
sub 3
sub 4
sub 5
sub 6
sub 7
sub 8
sub 9

複数のスレッドを待機したい時はそれぞれのハンドルでjoin()します。

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

fn main() {
    let h1 = thread::spawn(|| {
        for i in 0..10 {
            println!("sub1 {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
    let h2 = thread::spawn(|| {
        for i in 0..10 {
            println!("sub2 {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    println!("main");

    h1.join().unwrap();
    h2.join().unwrap();
}

moveクロージャで外の変数を使う

スレッドからメインスレッドの変数にアクセスしてみましょう。

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

fn main() {
    let mut v = vec![];

    let handle = thread::spawn(|| {
        v.push(1);
    });

    handle.join().unwrap();
}

上記のコードはエラーになります。

error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function
 --> src\main.rs:7:32
  |
7 |     let handle = thread::spawn(|| 
  |                                ^^ may outlive borrowed value `v`
8 |         v.push(1);
  |         - `v` is borrowed here
  |
note: function requires argument type to outlive `'static`
 --> src\main.rs:7:18
  |
7 |       let handle = thread::spawn(|| {
  |  __________________^
8 | |         v.push(1);
9 | |     });
  | |______^
help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
  |
7 |     let handle = thread::spawn(move || 
  |                                ++++

これを解決するにはクロージャ―にmoveを付けます。

use std::thread;

fn main() {
    let mut v = vec![];

    let handle = thread::spawn(move || {
        v.push(1);
        println!("{}", v[0]);  // 1
    });

    handle.join().unwrap();
}

moveを付けると外の変数の所有権がスレッドに移動します。
ですのでメインスレッドからvにアクセスしようとすると・・・

use std::thread;

fn main() {
    let mut v = vec![];

    let handle = thread::spawn(move || {
        v.push(1);
    });

    handle.join().unwrap();
    println!("{}", v[0]);  // 1
}

上記のコードは以下のエラーになります。

error[E0382]: borrow of moved value: `v`
  --> src\main.rs:11:20
   |
4  |     let mut v = vec![];
   |         ----- move occurs because `v` has type `Vec<i32>`, which does not implement the `Copy` trait
5  |
6  |     let handle = thread::spawn(move || 
   |                                ------- value moved into closure here
7  |         v.push(1);
   |         - variable moved due to use in closure
...
11 |     println!("{}", v[0]);  // 1
   |                    ^ value borrowed here after move

上記のエラーを簡単に言うと「vはクロージャにムーブされてるからメインスレッドでは使えないです。。」という意味になります。

おわりに

今回はRustのスレッドについて解説しました。
Rustでは簡単に並行処理できるようになっていますね。
なにか参考になれば幸いです



この記事のアンケートを送信する