これは何
これは Rust of Us - Chapter 3 で使う資料です。
2015/08/30 時点TRPL の cocurrency をベースにまとめています。
サンプルコードは、そのまま利用しています。
データを共有するのに大事な Trait
Rust の Concurrency で大事になる Trait が 2 つある。
- Sync
- Send
Sync
このトレイトを実装すると、コンパイラに複数のスレッド間でデータを共有しても問題無いことを教えます。
Sync を実装した例だと Arc という、アトミックなリファレンスカウントを使ってイミュータブルなデータをスレッド間で安全に共有することができる型がある。
Send
このトレイトを実装すると、スレッド間で安全にオーナーシップを受け渡すことができる。
チャンネルを使ってデータのやりとりをするときにも、このトレイトが実装されている必要がある。
例えば、FFI のラッピングのようなスレッドセーフでないデータをには、Send を実装すべきでない。
実装しないことでコンパイラが、スレッドからデータを移動すべきでないとエラーをだしてくれる。
スレッドを起動する
thread:spawn を使うとスレッドを起動することができる。Ruby と同じで、渡したクロージャが別スレッドとして実行される。
use std::thread;
fn main() {
thread::spawn(|| {
println!("Hello from a thread!");
});
}
spawn はハンドラーを返し、スレッドを join することでスレッドから値を返すことができる。
use std::thread;
fn main() {
let handle = thread::spawn(|| {
"Hello from a thread!"
});
println!("{}", handle.join().unwrap());
}
スレッドとデータを共有する
スレッド間でデータを共有するとき、スレッドセーフでないデータはコンパイル時にエラーとなる。
use std::thread;
fn main() {
let mut data = vec![1, 2, 3];
for i in 0..3 {
thread::spawn(move || {
data[i] += 1;
});
}
thread::sleep_ms(50);
}
// コンパイルエラー
//
// 8:17 error: capture of moved value: `data`
// data[i] += 1;
// ^~~~
スレッド間でデータを共有するには、前述の 2 つのトレイトを使う必要がある。
まず、Sync を利用するために Arc を使う。Arc は、標準の AtomicReferenceCount を提供する型で、複数のリファレンス間で同じデータのオーナーシップを
共有を可能にする。Arc は、リファレンスの数をカウントしてくれる。Arc のカウントは、コンパイラがアトミックに処理するようにしてくれる。
Atomic なため複数スレッド間でも安全にアクセスできる。Arc の操作は、アトミックになるようにコンパイラが処理をしてくれる。
Arc::clone() を呼びだすと内部カウントが増える。
use std::sync::Arc;
let mut data = Arc::new(vec![1, 2, 3]);
let data = data.clone();
ただし、Arc はラップするデータが Sync を実装していることを期待している。
そのため、このコードはエラーとなる。
:11:24 error: cannot borrow immutable borrowed content as mutable
:11 data[i] += 1;
^~~~
スレッド間で共有されるデータは、イミュータブルであることが望ましいがミュータブルなデータを扱いたい。
こんなときは、Mutex を使う。Mutext を使うと、そのタイミングで一つのスレッドだけが値を変更することを可能にします。
Mutext のみをスレッドで使うと、エラーが発生します。そのため、Arc でラップする必要がある。
error: the trait `core::marker::Send` is not implemented for the type `std::sync::mutex::MutexGuard>` [E0277]
thread::spawn(move || {
^~~~~~~~~~~~~
note: `std::sync::mutex::MutexGuard>` cannot be sent between threads safely
thread::spawn(move || {
^~~~~~~~~~~~~
Mutex の値を使うときは、Mutex::lock() を使う。lock() を使うと LockResult> が返ってくる
(MutexGuardが Sync を実装していないため、Arc 無しでスレッドで使えない)。
ロックを取得すると、データが開放されるまでロックを保持し続ける。
この方法では、親スレッドが子スレッドの情報を得るには、スレッドが終了するまで少し待つ必要がある。
use std::sync::Mutex;
fn main() {
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
for i in 0..3 {
let data = data.clone();
thread::spawn(move || {
let mut data = data.lock().unwrap();
data[i] += 1;
});
}
thread::sleep_ms(50);
}
lock() を実行したときに、他のスレッドがロックを獲得していると wait し、失敗すると Result が Error となるので、それでエラーハンドリングをする。
スレッドからデータを受けとる
スレッド間でデータをやりとりする別のやり型として、channels がある。この方法だと、待たなくてよい。
use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc;
fn main() {
let data = Arc::new(Mutex::new(0));
let (tx, rx) = mpsc::channel();
for _ in 0..10 {
let (data, tx) = (data.clone(), tx.clone());
thread::spawn(move || {
let mut data = data.lock().unwrap();
*data += 1;
tx.send(());
});
}
for _ in 0..10 {
rx.recv();
}
}
channel は Send を持っているデータを送ることができる。channel はデータを送るとシグナルも送信する。そうすることで、channel からデータを受けとることができる。
use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
for _ in 0..10 {
let tx = tx.clone();
thread::spawn(move || {
let answer = 42;
tx.send(answer);
});
}
rx.recv().ok().expect("Could not receive answer");
}
Panic!
panic! を使うと、実行しているスレッドをクラッシュすることができる。
パニックしたスレッドは Result を返すため、親スレッドは子スレッドがパニックしたかどうかを確認することができる。
A panic! will crash the currently executing thread. You can use Rust's threads as a simple isolation mechanism:
use std::thread;
let result = thread::spawn(move || {
panic!("oops!");
}).join();
assert!(result.is_err());
move クロージャとは
move クロージャを使うと、クロージャ内のデータのオーナーシップが移動する。