RustのThread(スレッド)で並行処理をする方法
- 作成日: 2023-05-24
- 更新日: 2023-12-24
- カテゴリ: Rust
RustのThreadで並行処理
Rustで並行処理をしたい場合に選択肢の1つに上がるのがスレッドです。
スレッドとはプログラムで並行処理を行うための仕組みです。
スレッドを起動し、関数を実行させ、それをメインスレッドから切り離して並行的に実行します。
この記事ではRustのスレッドについて解説します。
関連記事
RustでJSONの読み書きを行う
RustでTCPクライアント/サーバーを作る【ソケット通信】
Rustでファイルサイズを取得する方法【std::fs::metadata】
RustのBoxの使い方【ヒープにメモリを確保!初心者向け】
RustのResultでエラーハンドリング処理を行う
RustのThread(スレッド)で並行処理をする方法
RustのVecの使い方【vec!, push, pop】
Rustのassert!の使い方【assert!, assert_eq!, assert_ne!】
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では簡単に並行処理できるようになっていますね。
なにか参考になれば幸いです