スポンサーリンク

Rustでスレッドを使ってみる

Rustでスレッドを作成するにはspawnを使用する。よくあるように、一度作成したスレッドはハンドルを取得し、join()で待機する。スレッドとして走らせた関数の戻り値はjoin()の戻り値に入っているが、これはエラーなども扱うResultとして入っているのでunwrapして戻り値を取り出す。

1.引数のない関数を呼び出す

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);
}

2.引数を与えて呼び出す(マジックナンバー)

引数を与える場合、直接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(); }

3.メインスレッドのデータを引数渡しで処理する

メインスレッドのデータをスレッドの中で処理したい場合、メインスレッド側でデータが消えたりするとまずいので、そのまま参照を渡すことができない。

そこでデータを参照カウンタ付きのスマートポインタ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());

    // spawnmoveキーワードによりスレッドに変数の所有権が移ってしまうので
    // clone()で参照のコピーを作成する。Arccloneは参照カウンタが増えるだけなので負荷はほぼない
    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);


}

4.スレッドに可変な変数を渡して編集する

スレッド内で変数を編集したい場合、さらに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);


}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: