Arc
를 사용 시 주의할 부분에 대해서 포스팅을 하겠다.
use std::sync::{Arc, Mutex};
struct A {
b: Mutex<Option<Arc<B>>>,
}
struct B {
a: Mutex<Option<Arc<A>>>
}
fn main() {
let a = Arc::new(A { b: Mutex::new(None)});
let b = Arc::new(B { a: Mutex::new(None)});
println!("초기 a count: {}", Arc::strong_count(&a));
println!("초기 a count: {}", Arc::strong_count(&b));
println!("---- 순환 참조 생성 -----");
// b의 Arc를 복제해서 a에 넣는다.
*a.b.lock().unwrap() = Some(Arc::clone(&b));
println!("b를 가리킨 후 a count: {}", Arc::strong_count(&a));
println!("b를 가리킨 후 b count: {}", Arc::strong_count(&b));
// B가 A를 가리키게 합니다.
*b.a.lock().unwrap() = Some(Arc::clone(&a));
println!("a를 가리킨 후 a count: {}", Arc::strong_count(&a));
println!("a를 가리킨 후 b count: {}", Arc::strong_count(&b));
} // main function이 종료되면서 a와 b는 drop이 일어나므로 참조 카운트가 1씩 줄어든다.
// 그러나 A구조체는 B가 들고 있는 Arc때문에 메모리에서 해제되지 않음
// B 구조체도 마찬가지임
// 서로가 서로의 발목을 잡고 놓아주지 않는 상태가 되어버린 것.
a와 b 변수는 힙에 할당된 각각의 객체를 가르키고 있다
이제 main 스코프를 벗어날 때 drop이 되므로 참조 카운트가 1 감소하고 사라지지만, 문제는 힙에 할당된 각각의 객체 필드가 서로를 가르키고 있으므로 참조 카운트는 1이다. 즉, 참조 카운트가 0이 아니기 때문에 계속해서 힙에 존재한다. 그러나 main 함수가 종료되었지만, 더 이상 참조를 지울 수 있는 방법이 없으므로 메모리 누수가 발생한다고 보면 된다. 이것이 순환 참조의 문제이다.
그렇다면 이것을 방지하려면?
한 쪽에서 Weak
를 사용하면 된다.
use std::sync::{Arc, Mutex, Weak};
struct A {
b: Mutex<Option<Arc<B>>>,
}
struct B {
a: Mutex<Option<Weak<A>>>
}
fn main() {
let a = Arc::new(A { b: Mutex::new(None)});
let b = Arc::new(B { a: Mutex::new(None)});
println!("초기 a count: {}", Arc::strong_count(&a));
println!("초기 a count: {}", Arc::strong_count(&b));
println!("---- 순환 참조 생성 -----");
// b의 Arc를 복제해서 a에 넣는다.
*a.b.lock().unwrap() = Some(Arc::clone(&b));
println!("b를 가리킨 후 a count: {}", Arc::strong_count(&a));
println!("b를 가리킨 후 b count: {}", Arc::strong_count(&b));
// B가 A를 가리키게 합니다.
*b.a.lock().unwrap() = Some(Arc::downgrade(&a));
println!("a를 가리킨 후 a count: {}", Arc::strong_count(&a));
println!("a를 가리킨 후 b count: {}", Arc::strong_count(&b));
}
구조체 B
의 필드 a
의 값을 Weak
으로 바꾸면 어떻게 동작하는 지 설명하겠다.
a
의 참조 카운트가 1이므로 main 스코프를 벗어날 때 참조 카운트가 0이 되므로 힙에 존재하는 A객체는 해제된다.
이 때 필드인 b
또한 해제되므로 Arc의 참조 카운트는 1로 줄어든다. Arc 변수 b
또한 drop이 이루어지면서 참조 카운트가 0이되므로
B 객체도 해제된다. 메모리 누수 없이 해제가 잘된다.
Arc
를 사용 시 주의점에 대해서 알아봤는데 과연 순환 참조를 어느 로직에서 사용하는지 궁금했다. 실제로 사용해본적이 없다.
검색을 통해서 알아봤다.
struct Manager {
name: String,
employees: Mutex<Vec<Arc<Employee>>>, // 직원들을 소유
}
struct Employee {
name: String,
manager: Weak<Manager>, // 상사를 참조만 함 (소유 X)
}
Employee
의 경우 관리해 주는 상사를 참조만 하고 카운트를 증가시키지 않아 순환 참조가 되지 않도록 한다.
Manager
는 강한 참조로 직원을 필드로 가진다. 이렇게 하면 순환 참조 없이 안전하게 사용 가능하다.