ABOUT ME

-

Total
-
  • Rust: Ref, Arc, Rc, Mutex 문법 정리
    컴퓨터/Rust 2022. 12. 29. 11:26
    728x90
    반응형

    RARM

    @Rust 튜토리얼 웹사이트

     

    Rust 튜토리얼

    Hello World! 튜토리얼 소개 🎉 Rust 튜토리얼 기본편 영상을 위한 웹사이트입니다. 키보드 ⬅️/➡️ 버튼으로 페이지를 이동할 수 있습니다.

    hi-rust.github.io

    이 글은 가끔 가다 보이고 concurrent 하게 뭔 짓을 하려 할 때 사용해야 하는 문법에 대한 글이다.

    Rust의 핵심 기능 중 하나는 메모리 안전성을 보장하는 것입니다.

    Rust는 이를 소유권(ownership)과 빌림(borrowing)이라는 개념을 통해 처리합니다.

    소유권은 변수가 메모리의 어떤 부분을 소유하고 있음을 나타냅니다.

    이는 메모리 누수나 불필요한 복사를 방지하는 데 중요합니다. 빌림은 소유권이 있는 변수를 다른 변수가 일시적으로 참조하는 것을 나타냅니다.

    이는 데이터의 동시 접근 문제를 방지하는 데 중요합니다.

     

    Arc, Rc, RefCell, 그리고 Mutex는 소유권과 빌림 문제를 해결하는 데 도움이 되는 도구입니다.

    Arc와 Rc는 참조 카운팅을 통해 여러 변수가 동일한 데이터를 안전하게 공유할 수 있도록 합니다.

    RefCell은 런타임에 데이터의 변경 가능성을 확인하고, Mutex는 동시에 하나의 스레드만 데이터에 접근하도록 합니다.

     

    Ref

    Ref (Reference의 줄임말)는 동일한 데이터에 대한 여러 참조를 동시에 생성하고 사용할 수 있는 스마트 포인터의 한 유형이다.

    참조는 단일 스레드 내에서 데이터에 대한 공유 참조를 관리하는 데 사용되고 std::cell:RefCell을 사용하여 구현된다.

    RefCell은 런타임 borrowing 규칙을 사용하여 데이터가 unsafe한 방식으로 동시에 액세스 되지 않도록 합니다.

    RefCell을 사용하면 값이 변경되지 않은 경우에도 값에 대한 참조를 빌릴 수 있다.

    이는 일반 참조(&)에서는 불가능하고 값이 변경 가능한 경우에도 borrow rule이 적용되도록 하는 런타임  borrowing checking을 사용하여 수행된다.

     

    우선 Thread-safe manner한 Rust Code 예제

    두 개의 쓰레드를 만들어서 Arc<Mutex<i32>>의 값을 동시에 접근해 수정해도 thread-safe 해서 결과는 1+3이다.

    use std::sync::{Arc, Mutex};
    
    fn main() {
        let counter = Arc::new(Mutex::new(0)); // Create a thread-safe counter
    
        let counter_clone = Arc::clone(&counter);
        let handle1 = std::thread::spawn(move || {
            let mut counter = counter_clone.lock().unwrap();
            *counter += 1;
        });
    
        let counter_clone = Arc::clone(&counter);
        let handle2 = std::thread::spawn(move || {
            let mut counter = counter_clone.lock().unwrap();
            *counter += 3;
        });
    
        handle1.join().unwrap();
        handle2.join().unwrap();
    
        let counter = counter.lock().unwrap();
        println!("Counter: {}", *counter);
    }

     

    Ref example 1

    RefCell의 쉬운 예제이다. 같은 데이터의 여러 참조를 동시에 single thread safe하게 만들 수 있으므로

    y, z가 x를 같이 빌리고 borrow()borrow_mut()로 같은 꺼내서 dereference(*)을 이용해서 바로 수정했다.

    y를 수정하고 z에서 읽어도 x,y,z 모두 6이란 값을 갖고 있다.

    여기서 y.borrow()y.borrow_mut()을 변수에 저장하고 이후에 이 변수를 이용해서 바꾸면

    한 scope내에서 y를 다시 못 빌린다. 이후 예제에서 볼 것이다.

    use std::cell::RefCell;
    
    fn main() {
        let x = RefCell::new(5);
    
        let y = &x;
        let z = &x;
    
        *y.borrow_mut() += 1;  // Modify the value through a mutable reference
        assert_eq!(*z.borrow(), 6);  // The value can still be accessed through an immutable reference
    }

     

    Ref example 2

    이 예에서는 초기 값이 5인 c라는 RefCell이 있다. 이후 p라고 불리는 c에 대한 참조를 만든다.

    그런 다음, b라고 불리는 c의 mutable borrow을 만들고 그 값을 10으로 변경한다.

    마지막으로, 우리는 a라는 immutable borrow를 만들고 그 값이 10이라고 assert 한다.

    RefCell은 값이 immutable 한 위치에 저장되어 있더라도 값에 대한 muable 참조와 immutable 참조를 모두 가질 수 있는 유형이다.

    이는 runtime borrow checking을 사용하여 값에 액세스 할 때 데이터 레이스가 없는지 확인하기에 가능하다.

    또 아래 예에서 RefCell을 사용하면 c가 immutable 한 위치에 저장되어 있더라도 c의 값을 변환할 수 있다.

    이 기능은 여러 thread 간에 공유되는 구조에 저장된 값을 수정해야 하거나 immutable 위치에 저장된 구조에 저장된 값을 수정해야 할 때 유용하다.

    그러나 borrow rule을 위반할 경우 RefCell을 사용하면 런타임 오류가 발생할 수 있는데 borrow rule을 위반하는 방법으로 값을 대여하려고 하면 borrow checker (compiler)가 panic! 된다.

    use std::cell::RefCell;
    
    fn main() {
        let c = RefCell::new(5);
        let p = &c;
    
        let mut b = c.borrow_mut();
        *b = 10;
    
        let a = p.borrow();
        assert_eq!(*a, 10);
    }

    Arc

    Arc (Atomic Reference Count의 줄임말)는 여러 thread 간에 thread-safe 방식으로 데이터를 공유할 수 있는 데이터 유형이다.

    동일한 데이터에 대한 여러 thread의 참조를 허용한다는 점에서 Ref와 유사하지만 참조 카운트가 안전하게 업데이트되도록 원자(atomic) 연산을 사용한다.

     

    * atomic operation은 중단되지 않고 단일 단위의 작업으로 수행되는 연산이다.

    * 즉 Arc는 atomic 참조 카운팅을 제공하며, 이는 하나의 원자 단계에서 참조 카운팅 작업을 수행하여 thread-safe 방식으로 공유 데이터의 lifetime을 관리하는 데 사용될 수 있음을 의미한다.

     

    Arc example 1

    이 예에서는 정수 벡터를 만들어서 Arc로 감쌌다.

    그런 다음 3개의 thread를 생성하고 각 thread는 Arc의 복사본을 받는다.

    Arc의 참조 카운트는 복제될 때마다 증가하며, 복제본이 범위를 벗어나면(예: thread 실행이 완료되면)

    참조 카운트가 감소하고 이렇게 되면 thread가 데이터를 계속 사용하는 동안에는 데이터를 사용할 수 있다.

    use std::sync::Arc;
    use std::thread;
    
    fn main() {
        let data = Arc::new(vec![1, 2, 3]);
    
        for i in 0..3 {
            let data = data.clone();
            thread::spawn(move || {
                println!("Thread {}: {:?}", i, data);
            });
        }
    }

     

    Arc example 2

    좀 더 고급한 이 예에서는 여러 thread를 사용하여 병렬(parallel)로 업데이트하려는 숫자 벡터가 있다.

    그러나 data race가 없는지 확인하기 위해 Mutex를 사용하여 이 벡터에 대한 액세스를 동기화합니다.

    Arc를 사용하여 thread 간에 Mutex를 공유한다. 왜냐하면 Arc는 Rc(참조 카운트)의 thread-safe 버전으로, 여러 thread 간에 객체의 소유권을 공유할 수 있으니까.

    Arc::new를 호출하면 새 개체가 생성되고 초기 기준 카운트가 1로 설정된다.

    Arc::clone을 호출하면 참조 카운트가 1씩 증가하고 참조 카운트가 0에 도달하면 개체가 삭제된다.

    아래 예에서는 벡터를 포함하는 Mutex 주변에 Arc container를 만든다. 그런 다음 3개의 thread를 생성하고 Arc의 clone을 전달한다.

    각 thread는 lock을 사용하여 Mutex에서 lock을 획득(acquire)하고 벡터를 업데이트한 다음 unwrap을 사용하여 lock을 해제한다.

    모든 thread가 완료되면 lock을 다시 획득하고 결과를 출력한다.

    다시 말해 Arc는 thread-safe 하기 때문에 데이터 레이스에 대한 걱정 없이 thread 간에 Mutex를 공유할 수 있다.

     

    * data race: 2개 이상의 thread가 동일한 데이터에 동시에 액세스 하고 수정하려고 할 때 발생하는 상황

    use std::sync::{Arc, Mutex};
    use std::thread;
    
    fn main() {
        let data = Arc::new(Mutex::new(vec![1, 2, 3]));
    
        let mut handles = vec![];
    
        for i in 0..3 {
            let data = Arc::clone(&data);
            let handle = thread::spawn(move || {
                let mut data = data.lock().unwrap();
                data[i] += 1;
            });
            handles.push(handle);
        }
    
        for handle in handles {
            handle.join().unwrap();
        }
    
        let data = data.lock().unwrap();
        println!("Result: {:?}", data);
    }

     

    Arc(Mutex(RefCell(T))

    use std::cell::RefCell;
    use std::sync::{Arc, Mutex};
    use std::thread;
    
    fn main() {
        // RefCell은 동일한 스레드에서 가변 참조를 허용하지만, 여러 스레드에서 사용할 수 없습니다.
        // 따라서 Arc와 Mutex를 사용하여 스레드 간 안전성을 보장해야 합니다.
        // 아래 구조는 Arc를 사용하여 래퍼를 만든 다음, Mutex를 사용하여 가변 참조를 안전하게 관리합니다.
        let data = Arc::new(Mutex::new(RefCell::new(vec![1, 2, 3])));
    
        let mut handles = vec![];
    
        for _ in 0..3 {
            // data에 대한 Arc 클론을 생성하여 각 스레드가 독립적으로 참조하도록 합니다.
            let data_cloned = Arc::clone(&data);
            let handle = thread::spawn(move || {
                // Mutex를 잠근 다음, RefCell에 대한 가변 참조를 얻습니다.
                let data_locked = data_cloned.lock().unwrap();
                let mut data_ref_mut = data_locked.borrow_mut();
    
                // 벡터에 값을 추가합니다.
                data_ref_mut.push(4);
    
                // 스레드가 종료되면 Mutex가 자동으로 해제됩니다.
            });
            handles.push(handle);
        }
    
        // 모든 스레드가 완료되기를 기다립니다.
        for handle in handles {
            handle.join().unwrap();
        }
    
        // 결과를 출력합니다.
        let data_locked = data.lock().unwrap();
        println!("data: {:?}", data_locked.borrow());
    }

     

    Arc(RefCell(Mutex(T))

    고급 예제이다. 지금까지 본 Arc, RefCell, Mutex를 모두 합쳤다. (T를 포함하는 내부 변이 셀인 뮤텍스-벡터에 대한 참조 변환 스마트 포인터)

    Arc, RefCell, Mutex를 동시에 사용하려면 여러 thread에서 호출되지 않는 함수 또는 메서드와 같이 single thread 컨텍스트에서 사용할 수 있다. (왜냐하면 RefCell이 single thread syntax)

    아래 예에서는 Arc를 사용하여 데이터에 대한 참조 카운트 포인터를 만들고, RefCell을 사용하여 데이터를 mutable 하게 borrow 하고,

    Mutex를 사용하여 데이터에 대한 thread-safe 액세스를 제공한다. 이러한 방식으로, RefCell을 통해 데이터를 수정하고 Mutex를 통해 모두 single thread 컨텍스트에서 읽을 수 있다.

     

    use std::sync::{Arc, Mutex};
    use std::cell::RefCell;
    
    fn main() {
        let data = Arc::new(RefCell::new(Mutex::new(0)));
    
        // Modify the data through the RefCell
        // Error version: because of MutexGuard
        // 위에서 말한 RefCell 오류가 이것이다.
        // https://stackoverflow.com/questions/36773923/why-do-arc-and-mutex-allow-me-to-change-the-value-of-an-immutable-variable
        // let mut mutexr = *data.borrow_mut().lock().unwrap();
        // *mutexr = 10;
        
        *data.borrow_mut().lock().unwrap() = 3;
    
        // Read the data through the Mutex
        let binding = data.borrow();
        let value = binding.lock().unwrap();
        println!("Data: {}", *value);
    }

     

    언제 Arc(RefCell(Mutex))?

    1. Arc를 사용하여 thread 간에 데이터를 공유할 수 있다.
    2. 그러나 Arc는 단일 thread 내에서 mutable borrow을 허용하지 않아서 단일 thread 내에서 mutability을 허용하기 위해 RefCell을 사용할 수 있다.
    3. 이제 borrow_mut를 사용하여 단일 thread 내의 데이터를 borrow 할 수 있다.
    4. 하지만 여러 thread가 동시에 데이터를 borrow 하고 mutate할 수 있도록 하려면? Mutex를 사용하여 데이터에 대한 액세스를 동기화할 수 있다.
    5. 이제 lock()을 사용하여 여러 thread 내에서 데이터를 borrow하고 바꿀 수 있습니다.
    use std::cell::RefCell;
    use std::sync::Arc;
    use std::sync::Mutex;
    
    #[derive(Debug)]
    struct Data {
        value: i32,
    }
    
    impl Data {
        fn new(value: i32) -> Self {
            Self { value }
        }
    
        fn set_value(&mut self, new_value: i32) {
            self.value = new_value;
        }
    }
    
    fn main() {
        let data = Arc::new(RefCell::new(Mutex::new(Data::new(0))));
    
        let data_clone = data.clone();
        let data = data_clone.borrow_mut();
        let mut data = data.lock().unwrap();
        data.set_value(3);
        
        println!("{:#?}", data);
    }
    

    Rc

    Rc<T> ("reference counted" 줄임말)는 동일한 데이터에 대한 여러 참조를 가질 수 있는 스마트 포인터 유형이다.

    (C++ std::shared_ptr와 유사) Rc 값을 생성하면 기준 카운트가 증가한다. 기준 카운트가 0에 도달하면 데이터가 삭제된다.

    Rc는 동일한 데이터에 대한 참조를 여러 개 가지고 싶지만 데이터의 소유권을 가져가지 않으려는 경우에 유용하다.

    RefCell는 데이터에 대한 여러 참조를 허용하지만 데이터에 대한 여러 변경 가능한 참조를 허용하지 않기 때문에 RefCell와 결합할 때 특히 유용하다.

    RefCell를 Rc로 감싸면 이 두 개의 장점을 동시에 얻는다: 데이터에 대한 여러 참조를 가질 수 있고 데이터를 변환할 수도 있다.

     

    Rc example 1

    a는 b와 c의 child이고, b는 c의 child이다. a도 mutable 하게 borrow 되며 그 값은 10으로 바뀐다.

    그런 다음 a, b, c의 값을 immutable 하게 빌려 출력해서 a.value: 10, b.value: 6, c.value: 7이다.

    use std::rc::Rc;
    use std::cell::RefCell;
    
    struct Node {
        value: i32,
        children: Vec<Rc<RefCell<Node>>>,
    }
    
    fn main() {
        let a = Rc::new(RefCell::new(Node { value: 5, children: vec![] }));
        let b = Rc::new(RefCell::new(Node { value: 6, children: vec![a.clone()] }));
        let c = Rc::new(RefCell::new(Node { value: 7, children: vec![a.clone(), b.clone()] }));
    
        // We can borrow the value of a mutably and change it
        {
            let mut a_borrow = a.borrow_mut();
            a_borrow.value = 10;
        }
    
        // We can also borrow the value immutably and print it
        println!("a.value: {}", a.borrow().value);
        println!("b.value: {}", b.borrow().value);
        println!("c.value: {}", c.borrow().value);
    }
    
    /*
    a.value: 10
    b.value: 6
    c.value: 7
    */

     

    Rc example 2

    이 예에서는 원본 Rc<RefCell<T>>을 clone을 이용해서 2개의 clone을 만든다. 이렇게 하면 데이터의 참조 카운트가 증가하고 동일한 데이터에 대한 여러 참조를 가질 수 있다.

    그런 다음 RefCell에서 borrow_mut()을 사용하여 데이터를 변환하고 마지막으로, borrow()을 사용하여 데이터에 액세스 하고 콘솔에 출력한다.


    Rc는 thread-safe 또는 내부 변동성 (interior mutability)을 제공하지 않기 때문에 데이터를 직접 변환할 수 없다.

    데이터를 mutate 해야 할 경우 RefCell 또는 Mutex를 Rc와 함께 사용할 수 있다.

    use std::rc::Rc;
    use std::cell::RefCell;
    
    struct MyStruct {
        data: i32,
    }
    
    fn main() {
        let my_struct = Rc::new(RefCell::new(MyStruct { data: 10 }));
    
        // We can create multiple references to the same data using Rc<T>
        let my_struct_ref1 = my_struct.clone();
        let my_struct_ref2 = my_struct.clone();
    
        // We can mutate the data using RefCell<T>
        my_struct_ref1.borrow_mut().data = 20;
        my_struct_ref2.borrow_mut().data = 30;
    
        // We can access the data using RefCell<T>
        println!("{}", my_struct_ref1.borrow().data);  // 30
        println!("{}", my_struct_ref2.borrow().data);  // 30
    }

     

    Arc/RefCell/Rc/Mutex

    이 예에서는 Arc, RefCell, Rc 및 Mutex의 조합을 사용하여 공유 값에 대한 여러 참조를 허용하고 thread-safe 방식으로 값을 mutate 하고 액세스 한다.

     

    scope를 사용한 이유는 RefCell을 변수에 저장해서 borrow(_mut) 하면 그 영역이 끝나기 전까지 더 borrow를 못함

    scope를 이용하면 binding이 풀리니까 다시 사용할 수 있음

    use std::cell::RefCell;
    use std::rc::Rc;
    use std::sync::{Arc, Mutex};
    
    struct MyStruct {
        data: i32,
    }
    
    fn main() {
        let my_struct = Arc::new(Mutex::new(MyStruct { data: 10 }));
        let my_struct_refcell = Rc::new(RefCell::new(my_struct.clone()));
    
        // We can create multiple references to the same data using Rc<T>
        let my_struct_ref1 = my_struct_refcell.clone();
        let my_struct_ref2 = my_struct_refcell.clone();
    
        // We can mutate the data using RefCell<T> and Mutex<T>
        {
            let binding1 = my_struct_ref1.borrow_mut();
            let mut my_struct_mutex1 = binding1.lock().unwrap();
            my_struct_mutex1.data = 20;
        }
    
        {
            let binding2 = my_struct_ref2.borrow_mut();
            let mut my_struct_mutex2 = binding2.lock().unwrap();
            my_struct_mutex2.data = 30;
        }
        // We can access the data using RefCell<T> and Mutex<T>
        {
            let binding = my_struct_ref1.borrow();
            let my_struct_mutex = binding.lock().unwrap();
            println!("{}", my_struct_mutex.data); // 30
        }
    
        {
            let binding = my_struct_ref2.borrow();
            let my_struct_mutex = binding.lock().unwrap();
            println!("{}", my_struct_mutex.data); // 30
        }
    }

     

    Arc(Rc(RefCell(Mutex(T)))

    다 같이 쓰면 이런 느낌이다. 내용은 위와 같다.

    use std::cell::RefCell;
    use std::rc::Rc;
    use std::sync::{Arc, Mutex};
    
    struct MyStruct {
        data: i32,
    }
    
    fn main() {
        let my_struct = Arc::new(Rc::new(RefCell::new(Mutex::new(MyStruct { data: 10 }))));
    
        // We can create multiple references to the same data using Rc<T>
        let my_struct_ref1 = my_struct.clone();
        let my_struct_ref2 = my_struct.clone();
    
        // We can mutate the data using RefCell<T>, Mutex<T>, and Rc<T>
        {
            let binding1 = my_struct_ref1.borrow_mut();
            let mut my_struct_mutex1 = binding1.lock().unwrap();
            my_struct_mutex1.data = 20;
        }
    
        {
            let binding2 = my_struct_ref2.borrow_mut();
            let mut my_struct_mutex2 = binding2.lock().unwrap();
            my_struct_mutex2.data = 30;
        }
    
        // We can access the data using RefCell<T>, Mutex<T>, and Rc<T>
        {
            let binding = my_struct_ref1.borrow();
            let my_struct_mutex = binding.lock().unwrap();
            println!("{}", my_struct_mutex.data); // 30
        }
    
        {
            let binding = my_struct_ref2.borrow();
            let my_struct_mutex = binding.lock().unwrap();
            println!("{}", my_struct_mutex.data); // 30
        }
    }

     

    Arc<Rc<RefCell<Mutex<T>>>>데이터의 공유 소유권 및 thread-safety을 관리하는 데 사용되는 유형의 복잡한 조합이라고 볼 수 있다. 다시 하나씩 정리해 보면

    • Arc<T> 는 "원자 참조 카운팅"을 의미하며, 스마트 포인터의 일종으로 thread-safe 방식으로 동일한 데이터에 대한 여러 참조를 만들 수 있다. 참조가 추가되거나 제거될 때 참조 카운트가 안전하게 업데이트되도록 원자 연산을 사용한다.
    • Rc<T>는 "Reference Counting"의 약자로, 동일한 데이터에 대한 여러 참조가 가능한 또 다른 유형의 스마트 포인터이다. thread-safe는 아니지만 일반적으로 Arc보다 가볍고 효율적이다.
    • RefCell<T>는 "Reference Cell"의 약자로, single thread 환경에서 여러 mutable 참조를 데이터에 borrow 할 수 있는 유형이다. thread-safe가 아니며, 데이터가 동시에 수정되지 않도록 하기 위해 borrow chcker에게 의존한다.
    • Mutex<T>는 멀티 thread 환경에서 데이터에 대한 독점적인 액세스를 허용하는 유형인 "상호 제외"를 의미한다. lock을 사용하여 한 번에 하나의 thread만 데이터에 액세스 할 수 있도록 한다.

     

    그래서 Arc<Rc<RefCell<Mutex<T>>>> 를 정리하자면, 타입 T의 데이터의 thread 안전성을 보장하기 위해 Mutex로 감싼 다음, 단일 thread 환경에서 mutable borrow을 허용하기 위해 RefCell로 감싼다. 이 RefCell<T>로 그런 데이터에 대한 다중 참조를 허용하기 위해 Rc로 감싸지고, 마지막으로 Rc는 데이터에 대한 다중 참조를 허용하기 위해 Arc로 감쌌다.

     

    RealWorld example

    multiple scraper

    use reqwest::blocking::get;
    use scraper::{Html, Selector};
    use std::cell::RefCell;
    use std::collections::HashSet;
    use std::sync::{Arc, Mutex};
    use std::thread;
    
    // 웹 페이지에서 링크를 추출하는 함수
    fn extract_links(url: &str) -> Result<HashSet<String>, Box<dyn std::error::Error>> {
        let resp = get(url)?;
        let body = resp.text()?;
        let fragment = Html::parse_document(&body);
        let selector = Selector::parse("a").unwrap();
        let mut links = HashSet::new();
    
        for element in fragment.select(&selector) {
            if let Some(link) = element.value().attr("href") {
                links.insert(link.to_string());
            }
        }
    
        Ok(links)
    }
    
    fn main() {
        let start_url = "https://choiseokwon.tistory.com";
        let visited = Arc::new(Mutex::new(RefCell::new(HashSet::new())));
        let to_visit = Arc::new(Mutex::new(RefCell::new(vec![start_url.to_string()])));
    
        let mut handles = vec![];
    
        for _ in 0..10 {
            let visited_cloned = Arc::clone(&visited);
            let to_visit_cloned = Arc::clone(&to_visit);
    
            let handle = thread::spawn(move || {
                while let Some(url) = {
                    let to_visit_locked = to_visit_cloned.lock().unwrap();
                    let mut to_visit_ref_mut = to_visit_locked.borrow_mut();
                    to_visit_ref_mut.pop()
                } {
                    // 이미 방문한 URL인지 확인
                    let visited_already = {
                        let visited_locked = visited_cloned.lock().unwrap();
                        let visited_ref = visited_locked.borrow();
                        visited_ref.contains(&url)
                    };
    
                    if !visited_already {
                        println!("Fetching: {}", url);
                        let links = extract_links(&url);
    
                        if let Ok(links) = links {
                            // 새로운 링크를 to_visit에 추가
                            let to_visit_locked = to_visit_cloned.lock().unwrap();
                            let mut to_visit_ref_mut = to_visit_locked.borrow_mut();
                            for link in links {
                                to_visit_ref_mut.push(link);
                            }
                        }
    
                        // 방문한 URL을 visited에 추가
                        let visited_locked = visited_cloned.lock().unwrap();
                        let mut visited_ref_mut = visited_locked.borrow_mut();
                        visited_ref_mut.insert(url);
                    }
                }
            });
    
            handles.push(handle);
        }
    
        // 모든 스레드가 완료될 때까지 기다림
        for handle in handles {
            handle.join().unwrap();
        }
    }

     

    Summary

    기능 Arc RefCell Rc Mutex
    Concurrent access Yes No No Yes
    Mutability No Yes No Yes
    Owning Yes No Yes Yes
    Borrowing No Yes Yes No
    Unsafe No No No Yes (unlock)

     

    • Concurrent access: 여러 thread에서 데이터에 동시에 액세스 할 수 있는지 여부
    • Mutability: 데이터를 수정할 수 있는지 여부
    • Owning: 데이터가 컨테이너에 의해 소유되는지 여부
    • Borrowing: 컨테이너에서 데이터를 빌릴 수 있는지 여부
    • Unsafe: 데이터 컨테이너가 안전하지 않은 Rust 코드를 사용하는지 여부

    Arc (Atomic Reference Count):

    • 데이터에 대한 참조 수를 저장하여 데이터의 여러 소유권을 허용한다.
    • 참조 카운트는 atomic. 즉, 여러 thread에서 안전하게 액세스 하고 수정할 수 있다.
    • 데이터 mutate을 허용하지 않고 데이터에 대한 액세스만 허용한다.
    • 데이터에 대한 새 참조를 생성하기 위해 복제할 수 있다.
    use std::sync::Arc;
    
    let a = Arc::new(T);
    a.clone();

    RefCell (Reference Cell):

    • 데이터가 immutable 변수에 저장된 경우에도 데이터를 mutate 할 수 있다.
    • 데이터에 대한 다중 참조를 허용하지 않고 단일 참조만 허용한다.
    • thread 간에 공유할 수 없다.
    use std::cell::RefCell;
    
    let x = RefCell::new(5);
    
    let y = &x;
    let z = &x;
    
    *y.borrow_mut() += 1;

    Rc (Reference Counting):

    • Arc와 유사하게, 데이터에 대한 참조 수를 저장하여 데이터의 다중 소유를 허용한다.
    • 참조 카운트가 원자가 아니다. 즉, 여러 thread에서 안전하게 액세스 하거나 수정할 수 없다.
    • 데이터 mutate을 허용하지 않고 데이터에 대한 액세스만 허용한다.
    • 데이터에 대한 새 참조를 생성하기 위해 clone 될 수 있다.
    use std::rc::Rc;
    
    let rc = Rc::new(RefCell::new(1));
    
    let rc1 = rc.clone();
    let rc2 = rc.clone();

    Mutex (Mutual Exclusion):

    • 한 번에 하나의 thread만 액세스 하고 해당 thread에 저장된 데이터를 mutate 할 수 있다.
    • 다른 thread는 데이터를 액세스 하기 전에 데이터가 unlock 될 때까지 기다린다.
    • 여러 thread에서 액세스 할 수 있지만 한 번에 하나의 thread만 데이터에 액세스 하고 변환할 수 있다.
    use std::sync::Mutex;
    
    let mt = Mutex::new(0);
    let mut guard = mt.lock().unwrap();
    *guard = 5;
    Mutex::unlock(guard); // nightly
    728x90

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

    Rust: Send, Sync 정리  (0) 2022.12.29
    Rust: Closure Syntax  (1) 2022.12.27
    Rust: Generic Associated Types (GAT)  (0) 2022.12.19

    댓글