Arc, Rc, Refcell, 그리고 Mutex는 ownership과 borrow가 가진 문제를 해결할 수 있도록 도와준다.
동일한 데이터에 대한 여러 reference를 동시에 생성하고 사용할 수 있는 스마트 포인터. 단일 thread 내에서 데이터에 대한 shared reference를 관리하는 데 사용되고 std::cell::RefCell을 사용하여 구현된다. RefCell은 런타임 borrowing 규칙을 사용하여 데이터가 unsafe한 방식으로 동시에 접근할 수 없도록 한다.
use std::cell::RefCell;
fn main(){
let a = RefCell::new(5);
let b = &a;
let c = &a;
*b.borrow_mut() += 1;
assert_eq!(*c.borrow(), 6); // OK
}
use std::cell::RefCell;
fn main(){
let a = RefCell::new(5);
let b = &a;
let mut c = b.borrow_mut();
*c = 10;
let d = a.borrow();
assert_eq!(*d, 10); // main' panicked at 'already mutably borrowed: BorrowError'
}
multi thread에서 thread safe 방식으로 데이터를 공유할 수 있는 데이터 유형 (immutable reference)이다. Ref처럼 동일한 데이터에 대한 여러 thread의 reference를 허용하지만, reference count가 안전하게 업데이트 되도록 원자(atomic) 연산을 사용한다.
atomic operation:
- 중단되지 않고 단일 단위의 작업으로 수행되는 연산
- Arc는 atomic reference counting을 제공하며, 이는 하나의 원자 단계에서 reference count를 수행하여 thread-safe 방식으로 공유 데이터의 lifetime을 관리하는 데 사용될 수 있음을 의미한다.
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
use std::thread;
fn main(){
let data = Arc::new(Mutex::new(RefCell::new(vec![1, 2, 3])));
let mut handles = vec![];
for _ in 0..3 {
let data = data.clone();
let handle = thread::spawn(move || {
let data_locked = data.lock().unwrap();
let mut data_ref_mut = data_locked.borrow_mut();
data_ref_mut.push(4);
});
handles.push(handle);
}
for handle in handles{
handle.join().unwrap();
}
let data = data.lock().unwrap();
println!("Result {:?}", data); // Result RefCell { value: [1, 2, 3, 4, 4, 4] }
}
vec에 접근하기 전에 lock을 걸어 thread-safe 액세스를 제공하고, RefCell을 사용하여 mutable하게 빌려와 4를 push한다. 이러한 방식으로 RefCell을 통해 데이터를 수정하고 Mutex를 통해 모두 single thread 컨텍스트에서 읽을 수 있다.
Arc(Refcell(Mutex))
- thread 간에 데이터를 공유할 수 있다.
- Arc는 단일 thread 내에서 mutable borrow를 허용하지 않아서 단일 thread 내에서 mutability를 허용하기 위해 RefCell을 사용할 수 있다.
- 여러 thread가 동시에 데이터를 borrow하고 mutate할 수 있도록 하기 위해 Mutex를 사용하여 데이터에 대한 액세스를 동기화한다.
Rc<T>는 동일한 데이터에 대한 여러 참조를 가질 수 있는 스마트 포인터 유형이다. reference count가 0에 도달하면 데이터가 삭제된다. Rc는 소유권을 가지지 않고 reference를 여러 개 만들고 싶을 때 유용하다. RefCell는 데이터에 대한 여러 참조를 허용하지만 데이터에 대한 여러 변경 가능한 참조를 허용하지 않기 때문에 RefCell와 결합할 때 특히 유용하다. RefCell을 Rc로 감싸면 데이터에 대한 참조를 가질 수 있고, 변환할 수도 있다. clone()을 활용해 변수를 선언하면, count가 증가한다.
use std::cell::RefCell;
use std::rc::Rc;
struct MyStruct{
data: i32,
}
fn main(){
let my_struct = Rc::new(RefCell::new(MyStruct{data: 3}));
let my_struct_ref1 = my_struct.clone();
let my_struct_ref2 = my_struct.clone();
my_struct_ref1.borrow_mut().data = 10;
my_struct_ref2.borrow_mut().data = 20;
println!("{:?}", my_struct_ref1.borrow().data); // 20
println!("{:?}", my_struct_ref2.borrow().data); // 20
}
링크에 자세하고 명료하게 잘 정리 되어 있다. 참고하면 좋을 것 같다.
- shared ownership: Arc and Rc
- Arc만 비싼 대신 thread-safe- mutate something shared: Cell, RefCell, Mutex
- Mutex만 thread-safe
- Copy type -> Cell, non Copy type -> RefCell