프로그래밍백엔드 개발자

러스트 표준 라이브러리가 제공하는 스레드 동기화 방법은 무엇이며, 이를 선택할 때 어떤 점을 고려해야 할까요?

Hintsage AI 어시스턴트로 면접 통과

답변

러스트의 표준 라이브러리에서는 멀티스레딩 안전한 작업을 위한 기본 동기화 원시 타입을 제공합니다:

  • Mutex — 여러 스레드에서 데이터에 접근하기 위한 상호 배제 기능을 제공합니다;
  • RwLock — 여러 읽기자(read)가 동시에 접근할 수 있지만, 하나의 쓰기자(write)만 접근 가능하게 합니다;
  • Condvar — 이벤트에 따라 스레드를 깨우기 위한 조건 변수 원시 타입;
  • Atomic 타입 (AtomicBool, AtomicUsize 등) — 잠금 없이 하드웨어 수준에서의 읽기/쓰기를 지원합니다;
  • Arc (Atomic Reference Counted) — 객체에 대한 공동 소유를 위한 스레드 안전한 참조 카운팅.

선택:

  • 동시 읽기만 필요할 경우 — RwLock을 사용하세요 (Mutex보다 효율적임).
  • 단일 스레드의 간단한 동기화 접근이 필요할 경우 — Mutex.
  • 신호에 의한 동기화가 필요할 경우 (예: 새로운 요소를 기다림) — Condvar.
  • 원자 카운터/플래그를 위해 — Atomic.
  • 공동 소유(멀티스레드)용 — Arc.

예제:

use std::sync::{Arc, Mutex}; 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!("결과: {}", *counter.lock().unwrap()); }

트릭 질문

질문: Rust는 Mutex<T>를 사용함으로써 교착 상태(dealock)를 컴파일 단계에서 전부 방지한다고 보장하나요?

답변: 아니요. Rust는 소유권 및 차용 검사기를 통해 데이터에 대한 안전한 접근을 보장하지만, 언어 수준에서 교착 상태를 방지하지 않습니다. 교착 상태는 여러 Mutex의 잡기 순서가 어지럽거나 재귀적 잡기가 있을 때 논리적으로 발생합니다. 예:

use std::sync::Mutex; let lock1 = Mutex::new(0); let lock2 = Mutex::new(0); // 스레드 1: lock1 -> lock2, 스레드 2: lock2 -> lock1 ⇒ deadlock

역사

데이터 스트리밍 프로젝트에서 우연한 "멈춤" 현상이 발생했습니다. 개발자들이 Mutex 안에 Mutex를 중첩하여 사용하면서 잡기 순서를 엄격히 지키지 않아 교착 상태가 발생했습니다. 이는 프로세스를 강제로 종료하는 방법으로만 해소되었습니다.

역사

대규모 서비스에서 Mutex<Option<T>>에서 RwLock<T>로 대량 마이그레이션 했습니다. writable lock은 읽기 락보다 더 오래 걸릴 수 있음을 고려하지 않았습니다. 피크 로드 기간에 이는 쓰기를 위한 대기열로 인해 처리 지연이 수십 초로 늘어나게 했습니다.

역사

프로그래머들이 스레드를 아끼기 위해 Arc<Mutex<_>>를 수백 개 스레드에서 사용했습니다. 스케줄러의 미세한 작동 및 mutex 재사용으로 인해 기묘한 상호 대기가 발생했으며 성능은 5배 감소했습니다!