Rust의 스레드 안전

17229 단어 Rust

목적



스레드 안전을 어떻게 실현하는지 알아보기

요약



① 스레드간에 데이터가 원래 공유되지 않도록 한다.
②스레드간에 공유되는 경우는 같은 영역에 같은 타이밍으로 액세스하는 일이 없는지를 컴파일시에 체크해 준다.

스레드



thread::spawn(|arg|{body}); 에서 사용한다.

qiita.rs
use std::thread;

fn main() {

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

  println!("2", );

}

실행 결과


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は実行完了まで
  }

}



스레드간에 데이터 공유



스레드간에 데이터를 공유하지 않음



data라고 하는 vector의 값을 thread 마다 +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は実行完了まで
  }

}

다음과 같이 첫 번째 스레드가 실행될 때 data의 소유권이 첫 번째 스레드로 마이그레이션됩니다. 다른 스레드에서 사용할 수 없습니다.


소유권을 공유하는 메커니즘



소유권을 공유하기 위해 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는 멀티스레드에서는 사용할 수 없다.
멀티스레드 때 다른 스레드에 방해받지 않고 제대로 참조를 카운트업하거나 카운트다운할 수 있는 것을 확인할 수 없었기 때문이다.



멀티 스레드에서도 사용할 수있는 스마트 포인터 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에서는 같은 영역에 액세스하여 경합이 일어날 가능성이 있기 때문에 에러가 되고 있다.



Mutex를 사용하여 하나의 스레드에서 데이터 액세스를 허용합니다.



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は実行完了まで
  }

}

실행 결과

좋은 웹페이지 즐겨찾기