rust Arc 사용 시 주의점

wangki·2025년 7월 27일
0

Rust

목록 보기
41/54

Arc 사용 시 주의점

Arc를 사용 시 주의할 부분에 대해서 포스팅을 하겠다.

  1. 순환 참조
    참조 횟수가 0이 될 때 메모리를 해제한다.
    두 개 이상의 arc가 서로를 순환하며 가리키게 되면, 참조 횟수가 절대 0이 되지 않아 메모리 누수가 발생한다.
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를 사용 시 주의점에 대해서 알아봤는데 과연 순환 참조를 어느 로직에서 사용하는지 궁금했다. 실제로 사용해본적이 없다.
검색을 통해서 알아봤다.

  1. 조직도: 직원과 관리자
    회사의 조직도를 모델링 시, 한 명의 관리자가 여러 명의 직원을 관리한다.
struct Manager {
    name: String,
    employees: Mutex<Vec<Arc<Employee>>>, // 직원들을 소유
}

struct Employee {
    name: String,
    manager: Weak<Manager>, // 상사를 참조만 함 (소유 X)
}

Employee의 경우 관리해 주는 상사를 참조만 하고 카운트를 증가시키지 않아 순환 참조가 되지 않도록 한다.
Manager는 강한 참조로 직원을 필드로 가진다. 이렇게 하면 순환 참조 없이 안전하게 사용 가능하다.

0개의 댓글