スレッド
Rustはマルチスレッドでプログラムを実行する仕組みを備えています。ソースコードの中で、メインスレッドとは別のスレッドで実行したい処理を、明示的に宣言します。
スレッドの作成
別スレッドで実行するには、std::thread::spawn
を使用します。引数にはクロージャを渡します。
注意点として、クロージャからメインスレッドの変数にアクセスする場合は、クロージャの前にmove
キーワードを書いて、アクセスする変数の所有権を強制的にクロージャに移す必要があります。
use std::thread;
fn main() {
let data = String::from("Hello from thread!");
thread::spawn(move || {
println!("{}", data);
});
}
所有権をクロージャに移した場合、その変数はもはやメインスレッドの所有物ではないため、それ以降にメインスレッド内で参照しようとするとエラーになります。
use std::thread;
fn main() {
let data = String::from("Hello from thread!");
let handle = thread::spawn(move || {
println!("{}", data); // 所有権が移動
});
// 以下の行はコンパイルエラーになる
// println!("{}", data);
}
スレッドの実行を待つ
メインスレッド内で別スレッドを起動しても、メインスレッドは別スレッドが終了しているかを監視しません。すなわち、別スレッドの処理が途中であったとしても、メインスレッドの処理が終わってしまえばその時点で別スレッドの処理は打ち切られます。
もしメインスレッド側で別スレッドの処理を待ちたい場合は、thread::spawn
の戻り値(JoinHandle
型)を取り、join
メソッドを使います。
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..5 {
println!("スレッドから: {}", i);
thread::sleep(Duration::from_millis(500));
}
});
handle.join().unwrap();
println!("メインスレッド終了");
}
チャネル
チャネルは、スレッド間で非同期にデータをやり取りするための仕組みです。
チャネルは送信者と受信者との間に確立されます。一つのチャネルにおいて送信者は複数存在できますが、受信者は一つしか存在できません。
チャネルの作成
std::sync::mpsc::channel()
でチャネルを作成します。戻り値はタプル型で、一つ目が送信者、二つ目が受信者になります。
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
}
メッセージの送信
送信者のsend
メソッドでチャネルにメッセージを送信します。これは非同期に行われるため、送信後の処理は即座に行われます。
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(String::from("Hello, world!")).unwrap();
});
}
メッセージの受信
受信者のrecv
メソッドでメッセージを受信します。厳密にいえば、これは受信するまでスレッドをブロックします。メッセージを受信するまでの間に送信者がドロップされるなどしてチャネルが閉じられるとエラーを返します。
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(String::from("Hello, world!")).unwrap();
});
let received = rx.recv().unwrap();
println!("受信: {}", received);
}
複数の送信者
送信者のclone
メソッドで送信者を複製できます。これらを同時に使うことで、一つのチャネルで複数の送信者からメッセージを送れます。
複数のメッセージの受信
recv
メソッドはメッセージを一つ受け取るとスレッドのブロックを解除します。メッセージの数があらかじめわかっているのであれば、そのメッセージの数分だけfor
ループの中でrecv
メソッドを呼び出します。
そうでないならば、for received in 受信者
のように、受信者をイテレータとして扱うこともできます。このようにすると、チャネルが閉じられるまで都度メッセージの受信を待機することができます。
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
thread::spawn(move || {
tx1.send("スレッド1から").unwrap();
});
thread::spawn(move || {
tx.send("スレッド2から").unwrap();
});
for received in rx {
println!("受信: {}", received);
}
}
メッセージと所有権
メッセージで変数の値を送信した場合、その変数の所有権は受信者に移ります。つまり、送信後は送信者側で変数を参照できません。
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let data = String::from("所有権が移動するメッセージ");
// チャネルに送信 (所有権が移動)
tx.send(data).unwrap();
// 以下の行はコンパイルエラーになる
// 所有権が既に移動しているため、変数 `data` を参照できない
println!("{}", data);
});
let received = rx.recv().unwrap();
println!("受信: {}", received);
}