ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Rust] Cell, RefCell, Rc, Arc, Box
    dev/rust 2023. 3. 4. 12:10

    개념은 익숙하지만, 용어가 생소해서 그런지 자주 잊는 아이들이다

     

    Cell

    mutable 아닌 객체의 데이터를 수정할 수 있게한다

    - not thread-safe

    - only copy type

     

    RefCell

    싱글스레드용 내부 가변성을 제공, 보통은 컴파일타임에 mut인지 let인지 확인하지만, runtime checked borrowing rules. 

    아래 코드와 주석을 읽어보자

    let data = RefCell::new(true); // 생성
    let mut data_ref = data.borrow_mut(); // 빌림, 빌리고 있는 중에는 원본(data)을 사용할 수 없음. 
    *data_ref = false; // 할당
    drop(data_ref); // 빌림해제, 이제 원본 "data"를 사용할 수 있음.
    
    *data.borrow_mut() = false; // 이렇게 한줄로도 가능
    
    // 조금 더 안전하게 하려면..?
    match data.try_borrow_mut() { 
        Ok(mut r ) => *r = true,
        Err(_) => {}
    }

    그래서 RefCell은 Rc같은 스마트 포인터를 감싸는 용도로 사용한다 (Rc는 아래 설명하겠지만 reference counting의 약자로 소유자의 참조 횟수가 0이 되면 할당해제 되는 스마트 포인터이다)

    let data : RefCell<Rc<T>>;

     

    Mutex<T>

    러스트에서는 mutex만으로는 사용하지 않고 mutex로 감싼 객체로써 사용한다.

    Send/Sync Trait이구현 되어있 멀티스레드 환경에서 사용이 가능하며

    mutex는 내부 가변성을 제공한다. 즉 mut 타입이 아닌 객체이더라도 런타임에 값을 변경할 수 있다.

    ....
    let m = Mutex::new(5);
    
    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }

     

    스마트 포인터

    c/c++을 생각해보면 동적 메모리 할당/해제를 프로그래머가 직접 해줘야한다, 가비지 컬렉터가 없기 때문이다. 

    그래서 가비지 컬렉터가 없는 언어들은 스마트 포인터라는 개념이 있는것 같은데..

    스마트 포인터의 동작 방식은 동적할당 된 메모리를 아무도 사용하지 않게 되었을때 자동으로 메모리가 해제되는 방식이다.

    사용하지 않게 되었다는 기준은 스마트 포인터 종류 마다 조금씩 다르지만 기본적으로는 소유하고있는 소유자의 개수가 0이 되었을때, 그리고 단순히 함수영역을 벗어나면 메모리를 반환하는 형태이다.

     

    러스트에는 스마트 포인터의 종류가 세가지 있다 Box, Rc, Arc

     

    먼저 기본적인 Box 이다 

    Box<T>

    stack 메모리 영역인 아닌 heap메모리 영역에 동적 할당을 한다, c++의 unique_ptr과 유사하다.

    단일 소유권으로 하나의 객체에만 소유될 수 있다.

    fn foo() {
        let b = Box::new(5);
        println!("b = {}", b);
    } // foo()함수가 끝나면 Box로 할당되었던 b의 메모리도 같이 해제된다

     

    Rc<T> : Reference Counting

    c++의 shared_ptr과 유사하다. Box와는 다르게 여러 객체에 소유될 수 있다, 이름처럼 참조 카운트가 0이되는 순간 메모리가 반환된다.

    복수 소유권 객체이지만 멀티스레드로 부터 안전하지는 않다.
    Rc객체는 Send Trait이 구현되어있지 않기 때문인데 간단하게만 말하면 다른 스레드로 객체를 전달하기 위해서는 Send Trait이 구현되어있는 객체만 전달될 수 있다.
    따라서 싱글 스레드에서만 사용하도록 제한 되어있다고 보면 된다.

     

    Arc<T> : Atomic Reference Counting

    Rc의 멀티스레드 버전이다, 복수 소유권이고 Send/Sync Trait이 구현 되어있어 스레드 사이에 전달/접근이 가능하다.

    접근자체는 thread-safe하지만 수정은 그렇지 않다 그렇기 때문에 Mutex를 함께 사용하여 내부 수정을 안전하게 해야 한다.
    (꼭 접근 동기화할 필요가 없다면 Mutex를 사용하지 않을 수도 있다.)

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

     

     
Designed by Tistory.