Rust의 AtomicBool과 관련된 개념 및 라이브러리 총정리
Rust의 AtomicBool은 다중 스레드 환경에서 **불리언 값(boolean)**을 안전하게 공유하고 수정할 수 있도록 설계된 원자적(atomic) 데이터 타입입니다. 이 글에서는 AtomicBool의 개념, 사용 방법, 그리고 관련 라이브러리와 유용한 패턴을 종합적으로 정리합니다.
1. AtomicBool이란?
AtomicBool은 단일 비트 값(true
또는 false
)을 다중 스레드에서 안전하게 읽고 쓸 수 있는 원자적 타입입니다. std::sync::atomic 모듈에 포함되어 있으며, 내부적으로 CPU의 원자적 연산을 활용하여 동기화 문제를 해결합니다.
주요 특징
- 원자적 연산: 동시성 제약 없이 값의 읽기, 쓰기, 변경이 가능합니다.
- CAS(compare-and-swap) 연산 지원: 값의 상태를 비교하고 조건에 따라 원자적으로 변경.
- 메모리 순서 지정: 연산의 메모리 순서를 제어하기 위해
Ordering
을 사용.
AtomicBool 사용 예제
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
fn main() {
let is_ready = AtomicBool::new(false);
let handle = thread::spawn({
let is_ready = &is_ready;
move || {
is_ready.store(true, Ordering::SeqCst);
}
});
while !is_ready.load(Ordering::SeqCst) {
// Spin-wait
}
println!("Ready!");
handle.join().unwrap();
}
2. AtomicBool의 주요 메서드
메서드 | 설명 |
---|---|
new(value: bool) |
새로운 AtomicBool을 생성. 초기값을 설정. |
load(order: Ordering) |
AtomicBool의 현재 값을 읽음. |
store(value: bool, order: Ordering) |
AtomicBool에 새 값을 저장. |
compare_and_swap(current: bool, new: bool, order: Ordering) |
current 값과 비교 후, 동일하면 new 값으로 변경 (Rust 1.50부터 compare_exchange 로 대체). |
compare_exchange(current: bool, new: bool, success: Ordering, failure: Ordering) |
성공과 실패에 대해 메모리 순서를 지정하며 CAS 수행. |
fetch_and(value: bool, order: Ordering) |
현재 값과 주어진 값의 AND 연산 결과를 저장 후 이전 값을 반환. |
fetch_or(value: bool, order: Ordering) |
현재 값과 주어진 값의 OR 연산 결과를 저장 후 이전 값을 반환. |
3. 메모리 순서 (Ordering)
AtomicBool은 연산의 메모리 순서를 제어하기 위해 Ordering
열거형을 사용합니다. 이는 동기화의 강도와 성능 간의 균형을 결정합니다.
Ordering | 설명 |
---|---|
Relaxed |
메모리 순서를 신경 쓰지 않음. 성능은 높지만 동기화 보장이 약함. |
Acquire |
읽기 연산이 이전의 모든 쓰기 연산을 관찰할 수 있도록 보장. |
Release |
쓰기 연산이 이후의 모든 읽기 연산보다 먼저 실행되도록 보장. |
AcqRel |
읽기와 쓰기 모두 동기화. Acquire와 Release의 조합. |
SeqCst |
모든 스레드 간의 연산 순서를 강력히 보장 (가장 느리지만 안전). |
4. AtomicBool의 활용 사례
4.1 플래그 기반의 상태 확인
다중 스레드에서 작업 완료 여부를 플래그로 관리할 때 유용합니다.
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
fn main() {
let is_done = AtomicBool::new(false);
let handle = thread::spawn({
let is_done = &is_done;
move || {
// 작업 수행
println!("작업 완료!");
is_done.store(true, Ordering::SeqCst);
}
});
while !is_done.load(Ordering::SeqCst) {
// 작업 완료를 대기
}
println!("모든 작업이 완료되었습니다!");
handle.join().unwrap();
}
4.2 스핀 락(Spin Lock) 구현
AtomicBool을 사용해 간단한 스핀 락을 구현할 수 있습니다.
use std::sync::atomic::{AtomicBool, Ordering};
pub struct SpinLock {
flag: AtomicBool,
}
impl SpinLock {
pub fn new() -> Self {
SpinLock {
flag: AtomicBool::new(false),
}
}
pub fn lock(&self) {
while self.flag.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {}
}
pub fn unlock(&self) {
self.flag.store(false, Ordering::Release);
}
}
5. 관련 라이브러리와 개념
5.1 Crossbeam
Crossbeam은 고급 동시성 프로그래밍을 지원하는 라이브러리로, AtomicBool과 함께 안전하고 효율적인 작업을 구현할 수 있습니다. 예를 들어, AtomicCell
은 원자적으로 값을 관리할 수 있는 일반적인 구조를 제공합니다.
[dependencies]
crossbeam = "0.8"
use crossbeam::atomic::AtomicCell;
fn main() {
let atomic_value = AtomicCell::new(42);
atomic_value.store(100);
println!("Atomic Value: {}", atomic_value.load());
}
5.2 std::sync::Arc
와 결합
AtomicBool은 Arc와 결합하여 다중 스레드 간 안전한 데이터 공유에 활용됩니다.
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use std::thread;
fn main() {
let flag = Arc::new(AtomicBool::new(false));
let handles: Vec<_> = (0..5)
.map(|_| {
let flag = Arc::clone(&flag);
thread::spawn(move || {
while !flag.load(Ordering::Acquire) {
// 대기 중
}
println!("Flag set to true!");
})
})
.collect();
// 플래그 설정
flag.store(true, Ordering::Release);
for handle in handles {
handle.join().unwrap();
}
}
5.3 async-std와 Tokio에서의 사용
비동기 작업에서도 AtomicBool을 활용해 작업 흐름을 제어할 수 있습니다.
use std::sync::atomic::{AtomicBool, Ordering};
use tokio::task;
#[tokio::main]
async fn main() {
let is_ready = AtomicBool::new(false);
let task = task::spawn(async {
is_ready.store(true, Ordering::SeqCst);
});
while !is_ready.load(Ordering::SeqCst) {
// 비동기 대기
}
println!("Ready!");
task.await.unwrap();
}
6. AtomicBool의 장단점
장점 | 단점 |
---|---|
메모리 락 없이 동기화 가능 | 메모리 순서와 동기화를 잘못 설정하면 버그 발생 가능 |
성능이 뛰어남 (락 오버헤드 없음) | 복잡한 상태 관리에는 부적합하며 추가 도구가 필요 |
CAS와 메모리 순서 제어 지원 | 코드 복잡성 증가 (Ordering의 올바른 사용 필요) |
결론
Rust의 AtomicBool은 다중 스레드 환경에서 간단한 상태 제어와 동기화를 제공하는 강력한 도구입니다. 스핀 락, 플래그 관리, 상태 변경 감지 등 다양한 동시성 작업에서 활용될 수 있습니다. Ordering 개념을 잘 이해하고, 필요에 따라 Arc, Crossbeam 같은 도구와 결합하면 더욱 복잡한 동시성 문제를 안전하고 효율적으로 해결할 수 있습니다.