ABOUT ME

-

Total
-
  • Rust: Send, Sync 정리
    컴퓨터/Rust 2022. 12. 29. 17:42
    728x90
    반응형

    SS

    Rust의 Send 및 Sync trait은 데이터에 대한 안전한 동시 액세스를 보장하는 데 사용되는 관련 유형이다.

    그러나 type이 concurrent 설정에서 사용되도록 설계되지 않은 경우에도 여전히 race 조건이나 기타 동시 액세스 문제가 발생할 수 있기 때문에 Send 및 Sync를 구현한다고 해서 해당 유형이 thread-safe 하다는 보장은 없다는 점에 유의해야 한다.

    그러나 이러한 trait을 구현하는 것은 concurrent 설정에서 type을 사용하기 위해 필요한 조건이며, 그러한 맥락에서 type을 안전하게 사용할 수 있다는 보장을 제공한다.

     

    이전에 쓴 RefCell, Arc, Rc, Mutex를 보면 다음 표와 같다.

    Trait Arc RefCell Rc Mutex
    Send Yes Yes No Yes
    Sync Yes No No Yes
     

    Rust: Ref, Arc, Rc, Mutex 문법 정리

    RARM 이 글은 가끔 가다 보이고 concurrent 하게 뭔 짓을 하려 할 때 사용해야 하는 문법에 대한 글이다. Ref Ref (Reference의 줄임말)는 동일한 데이터에 대한 여러 참조를 동시에 생성하고 사용할 수 있

    choiseokwon.tistory.com

     

    Send

    Send trait 은 유형이 다른 thread로 이동해도 안전하다는 것을 나타낸다.

    즉, type이 Send을 구현하는 경우 해당 type의 인스턴스 소유권을 한 thread에서 다른 thread로 전송하는 것이 안전하다는 것을 의미한다.

    이 기능은 thread 간에 공유해야 하는 데이터가 포함된 유형에 유용하다. 동시 액세스로 인해 문제가 발생할 위험 없이 데이터를 다른 thread로 이동할 수 있기 때문이다.

     

    Send example 1

    String은 Send을 구현했기에 쓰레드 끼리 왔다 갔다 할 수 있다.

    use std::thread;
    
    fn main() {
        let data = "Hello, world!".to_string();
    
        let handle = thread::spawn(move || {
            println!("{}", data); // "Hello, world!" 출력
        });
    
        handle.join().unwrap();
    }

     

    Send example 2

    MyStruct라는 구조체에 Send를 구현하는 방법이다.

    이 예에서 MyStruct에는 Vec<String>이 포함되어 있으며, 문자열이 Sync를 구현하지 않기 때문에 기본적으로 Send를 구현하지 않는다.

    그러나 unsafe 키워드를 사용하여 수동으로 Send for MyStruct를 구현할 수 있다.

    이렇게 하면 Sync되지 않은 type을 포함하더라도 MyStruct의 인스턴스를 새 thread로 이동할 수 있다.

    use std::marker::Sync;
    use std::thread;
    
    struct MyStruct {
        data: Vec<String>,
    }
    
    unsafe impl Send for MyStruct {}
    
    fn main() {
        let my_struct = MyStruct {
            data: vec![String::from("hello"), String::from("world")],
        };
    
        let handle = thread::spawn(move || {
            println!("data in new thread: {:?}", my_struct.data);
        });
    
        handle.join().unwrap();
    }

     

    Send, Sync는 marker trait로 메소드가 없고 타입에 대한 특정 property를 옮기는 데에만 쓰이는 trait이다.

    unsafe impl Send for MyStruct {} 구문은 Send trait의 요구 사항을 충족하지 않더라도 MyStruct 유형을 thread 경계를 넘어 전송하는 것이 안전하다는 것을 Rust 컴파일러에게 알려주는 방법이다.

    이를 "안전하지 않은 특성 구현"이라고 하며, type이 thread 간에 안전하게 전송될 수 있다고 확신하는 경우에만 사용해야 한다.

     

    그런데 위처럼 구현만 한다고 thread-safe 해지는 것은 아니다. 단순히 쓰레드 사이에서 공유할 수 있음만을 알려줄 뿐이고 문제가 발생할 수 있다. 그러면 Mutex나 Atomic과 같은 타입으로 변경시켜줘야 한다.

    use std::marker::Send;
    use std::thread;
    use std::sync::Mutex;
    
    struct MyStruct {
        strings: Mutex<Vec<String>>,
        integer: i32,
    }
    
    unsafe impl Send for MyStruct {}
    
    
    fn main() {
        let my_struct = MyStruct {
            strings: Mutex::new(vec![String::from("hello"), String::from("world")]),
            integer: 10,
        };
    
        let handle = thread::spawn(move || {
            println!("data in new thread: {:?}", my_struct.strings.lock().unwrap());
        });
    
        handle.join().unwrap();
    }

     

    Sync

    반면 Sync trait은 thread 간에 공유할 수 있는 type을 나타낸다.

    즉, type이 Sync를 구현하는 경우 해당 type의 인스턴스에 대한 여러 참조가 다른 thread 에 있는 것이 안전하다는 것을 의미한다.

    동시 액세스로 인해 문제가 발생하지 않고 thread  간에 데이터를 안전하게 공유할 수 있으므로 여러 thread 에서 읽어야 하는 데이터를 포함하는 type에 유용합니다.

     

    Sync example 1

    Send example1 에서 본 String은 Sync까지 구현되어있어서 여러 쓰레드 끼리 공유할 수 있다. (safe)

    * Arc는 여러 thread 간에 thread-safe 방식으로 데이터를 공유할 수 있는 데이터 유형

    use std::sync::Arc;
    
    fn main() {
        let string = Arc::new("Hello".to_string());
    
        let string_ref1 = string.clone();
        let string_ref2 = string.clone();
    
        println!("{}", string_ref1); // Hello
        println!("{}", string_ref2); // Hello
    }

     

    Sync가 필요한 예제는 생각이 나지 않고 어렵다. 왜냐하면 RefCell처럼 Send가 구현되고 Sync가 구현되지 않은 것은 어렵기 때문이다.

     

    Negative trait

    #[lang = "not_send"]
    pub trait !Send {}

    !Send 나 !Sync가 있는데 !Send trait 은 이른바 "부정적 특성"으로,  type이 특정 속성을 가지고 있지 않음을 나타내는 특성이다.

    이 경우 !Send는 유형이 Send 특성을 구현하지 않음을 나타낸다.

    unsafe한 !Send trait를 구현할 수 없는데 어떤 type이 부정적 특성이 나타내는 특성을 가지고 있는지 확인할 수 있는 방법이 없기 때문이다.

    'negative impls cannot be unsafe' 라는 오류가 나오는 이유다. 대신, !Send 를 사용하면 이전 예제와 같이 함수의 인수 type을 제한하거나 값을 반환하는 특성을 보낸다.

    이렇게 하면 Send trait을 구현하지 않는 type으로 함수가 호출되지 않도록 할 수 있다.

    use std::cell::Cell;
    
    struct MyStruct {
        data: Cell<i32>,
    }
    
    unsafe impl !Send for MyStruct {}
    
    fn main() {
        let my_struct = MyStruct { data: Cell::new(10) };
    
        let handle = std::thread::spawn(move || {
            // This line will fail to compile, because MyStruct does not implement Send
            println!("{}", my_struct.data.get());
        });
    
        handle.join().unwrap();
    }

     

    Marker trait

    T: MyMarkerTrait, trait bound을 사용하여 일반 type T가 MyMarkerTrait trait을 구현해야 함을 지정할 수 있다.

    마커 trait을 사용하면 type 자체에 추가 기능이나 요구 사항을 추가하지 않고도 일반 type에 대한 제약 조건을 지정할 수 있다는 이점이 있다.

    이렇게 하면 마커 trait을 사용하여 해당 trait이 실제로 구현되는 방법에 대해 걱정할 필요 없이 해당 type에 특정 속성이 있음을 나타낼 수 있으므로 코드를 보다 유연하고 쉽게 추론할 수 있다.

    trait Serializable: MyMarkerTrait {}
    
    struct MyStruct {
        // ...
    }
    
    impl Serializable for MyStruct {}

     

    참고

     

    확장 가능한 동시성: Sync와 Send - The Rust Programming Language

    흥미롭게도, 러스트 언어는 매우 적은 숫자의 동시성 기능을 갖고 있습니다. 우리가 이 장에서 여지껏 얘기해 온 거의 모든 동시성 기능들이 언어의 부분이 아니라 표준 라이브러리의 영역이었

    rinthel.github.io

     

     

    Send in std::marker - Rust

    NonNull pointers are not Send because the data they reference may be aliased.

    doc.rust-lang.org

     

     

    Sync in std::marker - Rust

    NonNull pointers are not Sync because the data they reference may be aliased.

    doc.rust-lang.org

     

    728x90

    '컴퓨터 > Rust' 카테고리의 다른 글

    Rust: AsMut, AsRef, Deref, DerefMut 정리  (1) 2022.12.31
    Rust: Ref, Arc, Rc, Mutex 문법 정리  (0) 2022.12.29
    Rust: Closure Syntax  (1) 2022.12.27

    댓글