Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

11. Send & Sync — 컴파일 타임 동시성 증명 🟠

학습 목표: Rust의 SendSync 자동 트레이트(Auto-traits)가 어떻게 컴파일러를 동시성 감사관으로 변모시키는지 배웁니다. 어떤 타입이 스레드 경계를 넘을 수 있는지, 어떤 타입을 안전하게 공유할 수 있는지를 런타임 비용 없이 컴파일 타임에 증명하는 법을 익힙니다.

관련 장: 04장 (역량 토큰), 09장 (팬텀 타입), 10장 (const fn 증명)


문제 요망: 안전망 없는 동시 접근

시스템 프로그래밍에서 주변 장치, 공유 버퍼, 전역 상태는 메인 루프, 인터럽트 핸들러, DMA 콜백 등 다양한 컨텍스트에서 접근됩니다. C 언어에서는 volatile 키워드가 최적화를 막아줄 뿐, 데이터 경합(Data race)에 대해서는 아무런 보호도 제공하지 못합니다.

/* C — 인터럽트와 메인 루프 간의 경합 발생 가능 */
volatile uint32_t sensor_buf[64];
volatile uint32_t buf_index = 0;

void SENSOR_IRQHandler(void) {
    sensor_buf[buf_index++] = read_sensor(); // 경합: buf_index 읽기 + 쓰기
}

이런 동시성 버그는 재현하기 매우 어렵고, 주로 운영 환경에서 부하가 걸릴 때 간헐적으로 발생합니다.


Send와 Sync가 증명하는 것

Rust는 컴파일러가 자동으로 유도하는 두 가지 마커 트레이트를 정의합니다.

트레이트증명 내용비공식적 의미
SendT 타입의 값을 다른 스레드로 안전하게 이동할 수 있음"스레드 경계를 넘을 수 있음"
Sync공유 참조 &T를 여러 스레드에서 안전하게 사용할 수 있음"여러 스레드에서 동시에 읽을 수 있음"

이들은 **자동 트레이트(Auto-traits)**입니다. 구조체의 모든 필드가 Send이면 구조체도 Send가 됩니다. 하나라도 !Send인 필드가 있다면 전체 구조체도 스레드 간 이동이 불가능해집니다.


주변 장치 핸들의 스레드 격리

하드웨어 레지스터는 특정 메모리 주소에 고정되어 있으며, 원칙적으로 단일 실행 컨텍스트에서만 접근해야 합니다. 원시 포인터(*const T)는 기본적으로 !Send!Sync이므로, 이를 포함하는 핸들은 자동으로 특정 스레드에 갇히게 됩니다.

/// UART 핸들은 원시 포인터를 포함하므로 자동으로 !Send 및 !Sync입니다.
pub struct Uart { regs: *const u32 }

fn main() {
    let uart = Uart::new(0x4000_1000);
    
    // ❌ 컴파일 에러: Uart는 !Send이므로 다른 스레드로 보낼 수 없음
    // std::thread::spawn(move || { uart.write_byte(b'B'); });
}

Mutex를 통한 Sync 복구

Cell<T>이나 RefCell<T>은 동기화 장치가 없으므로 !Sync입니다. 하지만 여러 스레드에서 데이터를 공유해야 할 때 Mutex<T>로 감싸면 컴파일러는 이를 안전하다고 판단합니다.

TSend이면, Mutex<T>Send + Sync입니다.

이는 단순히 동기화하는 것을 넘어, 동기화되었음을 증명하는 것입니다. Mutex::lock()을 통해서만 데이터에 접근할 수 있도록 강제하기 때문에 "잠금(lock)을 잊어버리는 실수"는 구조적으로 불가능해집니다.


핵심 요약

  1. 컴파일 타임 동시성 감사SendSync는 타입의 구조를 분석하여 동시성 안전성을 정적으로 증명합니다. 런타임 비용은 전혀 없습니다.
  2. 원시 포인터와 자동 제외 — 하드웨어 핸들은 기본적으로 스레드에 격리되며, 필요한 경우에만 명시적으로 권한을 부여하게 됩니다.
  3. 구조적인 잠금 강제Mutex 패턴은 잠금 없이 데이터에 접근하는 행위를 컴파일 타임에 원천 차단합니다.
  4. 함수 경계의 정리F: Send + 'static과 같은 트레이트 바운드는 함수 호출자가 스레드 안전성을 증명해야 한다는 '정리(Theorem)'가 됩니다.
  5. 통합된 안전성 — 타입 상태, 팬텀 타입, const fn 증명과 결합하여 프로토콜 순서, 권한, 값의 범위, 그리고 동시성 안전성까지 완벽한 안전망을 구축할 수 있습니다.