LoginSignup
4
5

More than 3 years have passed since last update.

Rustのスレッド安全

Last updated at Posted at 2021-02-27

目的

スレッド安全をどのように実現しているか知る

まとめ

①スレッド間でデータがそもそも共有されないようにする。
②スレッド間で共有される場合は同じ領域に同じタイミングでアクセスすることがないかをコンパイル時にチェックしてくれる。

スレッド

thread::spawn(|arg|{body});で使用する。

qiita.rs
use std::thread;

fn main() {

  let handle = thread::spawn(|| {
    println!("1", );
  });

  println!("2", );

}

実行結果
image.png

10個のスレッドの実行。どの順番で実行されるかはわからない。

qiita.rs
use std::thread;
fn main() {

  let mut handles=Vec::new();
  for x in 0..10{
    handles.push(thread::spawn(move || {
      println!("{}", x);//moveさせないと参照のスコープから外れてしまう。
    }))
  }

  for handle in handles{
    let _=handle.join();//joinは実行完了まで
  }

}

image.png

スレッド間でのデータの共有

スレッド間でデータを共有させない

dataというvectorの値をスレッド毎に+1する処理。
以下は、スレッド間でデータを共有する場合に安全でなければ、コンパイルエラーになる例。

qiita.rs
use std::thread;

fn main() {

  let mut data=[0,1];//共有データ
  let mut handles=Vec::new();
  for x in 0..2{
    handles.push(thread::spawn(move || {
      data[x]+=1;
      println!("{}", x);
    }))
  }

  for handle in handles{
    let _=handle.join();//joinは実行完了まで
  }

}

以下のように、1個目のスレッドが実行された際にdataの所有権が1つ目のスレッドに移行してしまい。他のスレッドで使用できなくなる。
image.png

所有権を共有する仕組み

所有権を共有するために、Rcという参照カウンタ式のスマートポインタがある。
C++のshared_ptr同様参照カウンタが0になると自動的に解放される。
デフォルトは不変。

qiita.rs
use std::rc::Rc;

fn main() {

  let data=Rc::new([0,1]);
  println!("参照数 {}", Rc::strong_count(&data));//1
  {
    let data2=data.clone();//参照カウントUP
    println!("参照数 {}", Rc::strong_count(&data));//2
    for x in 0..2{
      println!("{}", data2[x]);
    }

  }//解放

  println!("参照数 {}", Rc::strong_count(&data));//1

}

Rcを使ってデータを共有してみる

qiita.rs
use std::rc::Rc;
use std::thread;

fn main() {
  let mut handles=Vec::new();
  let mut data=Rc::new([0,1]);//共有データ

  for x in 0..2{
    let mut ref_data=data.clone();//参照カウントUP

    handles.push(thread::spawn(move || {
      ref_data[x]+=1;
      println!("{}", ref_data[x]);
    }))
  }

  for handle in handles{
    let _=handle.join();//joinは実行完了まで
  }

}

データをスレッド間では安全に渡せないとエラーになる。Rcはマルチスレッドでは使えない。
マルチスレッドの時に別のスレッドに妨害されず正しく参照をカウントアップしたり、カウントダウンできることを確認できていないためらしい。

image.png

マルチスレッドでも使用可能なスマートポインタArc(Automatically Reference Counted).

RcをArcに変えてコンパイル。

qiita.rs
use std::sync::Arc;
use std::thread;

fn main() {
  let mut handles=Vec::new();
  let mut data=Arc::new([0,1]);//共有データ

  for x in 0..2{
    let mut ref_data=data.clone();//参照カウントUP

    handles.push(thread::spawn(move || {
      ref_data[x]+=1;
      println!("{}", ref_data[x]);
    }))
  }

  for handle in handles{
    let _=handle.join();//joinは実行完了まで
  }

}

Arcは書き換えできないと怒られる。
ref_data[x/2]+=1の時、スレッド0と1では同じ領域にアクセスして競合が起こる可能性があるためエラーになっている。

image.png

Mutexを使用して1つのスレッドからデータのアクセスを許可する

lock()を使用するとデータへのロックと解除が自動で行われ、競合が起こらないことが保証される。

qiita.rs

use std::sync::{Arc,Mutex};
use std::thread;

fn main() {
  let mut handles=Vec::new();
  let mut data=Arc::new(Mutex::new([0,1]));//共有データ

  for x in 0..2{
    let ref_data=data.clone();//参照カウントUP

    handles.push(thread::spawn(move || {
      let mut data=ref_data.lock().unwrap();
      data[x]+=1;
      println!("{}", data[x]);
    }))
  }

  for handle in handles{
    let _=handle.join();//joinは実行完了まで
  }

}

実行結果

image.png

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5