Rustでスレッドを作成するにはspawnを使用する。よくあるように、一度作成したスレッドはハンドルを取得し、join()で待機する。スレッドとして走らせた関数の戻り値はjoin()の戻り値に入っているが、これはエラーなども扱うResultとして入っているのでunwrapして戻り値を取り出す。
spawnには原則として引数なしの関数しか渡せない。
// スレッドとして実行する関数 // 引数をとることはできないが、任意の型の戻り値を返すことはできる fn my_sum()->i32 { let mut sum = 0; for i in 0..50 { println!("{}", i); sum += i; } return sum; }
fn main(){ // 関数が引数を一つも取らない場合、spawnに関数名を渡すだけでスレッドが作成される let thread_handle1 = std::thread::spawn(my_sum); let thread_handle2 = std::thread::spawn(my_sum); // my_sumは引数をとることはできないが、任意の型の戻り値を返すことはできる let result1:i32 = thread_handle1.join().unwrap(); let result2:i32 = thread_handle2.join().unwrap(); println!("results are {} , {} ", result1,result2); }
引数を与える場合、直接spawnに与えることができないので、クロージャ(ラムダ関数のようなもの)を与える。Rustのクロージャは頭に || をつける。
以下の例では、「myprintに'a' 'b' 50 というマジックナンバーを与えて呼び出す」という処理を行う引数なしの関数をspawnに渡している。
// 値を表示する関数 fn myprint(mark : char,count:i32) { for i in 0..count{ println!("{} -> {}",mark,i); } }
fn main() { // spawnにはクロージャ(≒ラムダ関数)を渡す。 || はクロージャを表す。 // 即ち、「myprintを引数付きで渡す」という処理を行う、引数なしの関数を渡している。 let thread_handle1 = std::thread::spawn(|| myprint( 'a',50)); let thread_handle2 = std::thread::spawn(|| myprint( 'b',50)); // 終了まで待機 thread_handle1.join().unwrap(); thread_handle2.join().unwrap(); }
メインスレッドのデータをスレッドの中で処理したい場合、メインスレッド側でデータが消えたりするとまずいので、そのまま参照を渡すことができない。
そこでデータを参照カウンタ付きのスマートポインタArcで渡すことで、メインスレッドを含め各スレッドが処理している最中にデータが消える危険性を排除する。
// vecの不変参照を受け取り、表示する関数 fn myprint(input: &Vec<i32>) { for i in input.iter(){ println!("i=={}",i); } }
// テスト用のVecのデータを作成する関数 fn create_data()->Vec<i32>{ let mut data = vec![]; for k in 0..100{ data.push(k); } return data; }
fn main() { // スレッドに渡すためにはArcでラップする必要がある let data1 = std::sync::Arc::new(create_data()); let data2 = std::sync::Arc::new(create_data()); // spawnのmoveキーワードによりスレッドに変数の所有権が移ってしまうので // clone()で参照のコピーを作成する。Arcのcloneは参照カウンタが増えるだけなので負荷はほぼない let d1_clone = data1.clone(); let d2_clone = data2.clone(); let thread_handle1 = std::thread::spawn(move || myprint( &d1_clone)); // d1_cloneの所有権が移動 let thread_handle2 = std::thread::spawn(move || myprint( &d2_clone)); // d2_cloneの所有権が移動 // 終了まで待機 thread_handle1.join().unwrap(); thread_handle2.join().unwrap(); // データの所有権は移行していないので、ここで表示できる println!("data: {:?}", data1); println!("data: {:?}", data2); }
スレッド内で変数を編集したい場合、さらにMutexでラップする。
// 配列を受け取り、二倍にする関数 fn double(input: &std::sync::Mutex<Vec<i32>>) { let mut vecdata = input.lock().unwrap(); for i in vecdata.iter_mut() { *i *= 2; } }
fn main() { let data1 = std::sync::Arc::new(std::sync::Mutex::new(vec![1,2,3,4,5])); let data2 = std::sync::Arc::new(std::sync::Mutex::new(vec![10,20,30,40,50])); let mut d1_clone = data1.clone(); let mut d2_clone = data2.clone(); let thread_handle1 = std::thread::spawn(move || double(&d1_clone)); let thread_handle2 = std::thread::spawn(move || double(&d2_clone)); thread_handle1.join().unwrap(); thread_handle2.join().unwrap(); println!("data1: {:?}", &data1); println!("data2: {:?}", &data2); }