rustで他スレッドから通知を待ち合わせるにはMutexとCondvar型の条件変数を用います。使い方は一般的な条件変数と同じで、待ち合わせる方のスレッドはMutexのロックを取得したあと条件変数に対してwaitを呼び出します。通知を行う方のスレッドは条件変数に対してnotifyを呼び出します。
実際に条件変数による待ち合わせを行うコードを以下に示します。
ポイントは以下の通り。
- 条件変数はMutex変数と対で用います。タプルでまとめて管理すると良いかもしれません。
- 条件変数とMutex変数はスレッドをまたいで共有されるのでArcでくるみ、Arc::clone()で複製を作って使用します。
- cond.wait(mutex)は内部でmutexのロックを外してnotifyを待ちます。notifyを受けてwaitから復帰する時にはロックを取得した状態で復帰します。
- wait関数は実装の都合上notifyを受けていないのに復帰することがあります(偽wakeup)。偽wakeupのケースをケアするためにwaitから復帰した直後にmutexの内容を検査します。
use std::sync::Condvar;
use std::sync::Mutex;
use std::sync::Arc;
use std::thread;
use std::time;
fn main() {
let cond = Arc::new(Condvar::new()); // 条件変数
let go = Arc::new(Mutex::new(false)); // ミューテックス変数
// waitスレッド起動
let cond_c = Arc::clone(&cond);
let go_c = Arc::clone(&go);
let t1 = thread::spawn(move || {
loop {
// ロック取得
let mut go = go_c.lock().unwrap();
while !*go { // 偽wakeupの場合は再度waitする
// wait: 内部でロックを外しnotifyを待つ
// waitから復帰する際にはミューテックスのlockを取得している
go = cond_c.wait(go).unwrap();
}
println!("wakeup!");
*go = false;
}
});
// notifyスレッド起動
let cond_p = Arc::clone(&cond);
let go_p = Arc::clone(&go);
let t2 = thread::spawn(move || {
loop {
thread::sleep(time::Duration::new(0, 1_000_000_000u32));
println!("notify!");
// ミューテックスのロック取得
let mut go = go_p.lock().unwrap();
*go = true;
// 条件変数に対してnotify
cond_p.notify_all();
}
});
t1.join().unwrap();
t2.join().unwrap();
}
- waitの他にタイムアウトを指定できるwait_timeが存在します。
- また、偽wakeupのケアをしてくれるwait_whileも存在します。