본문 바로가기
C.W.K.
Stream
Lesson 03 of 06 · published

공유 가변 상태 (Mutex/RwLock)

~14 min · tauri, mutex, concurrency, rust

Level 0웹 관광객
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"command는 여럿, 메모리는 하나, 어쩌면 동시에. 락이 걔들이 서로를 밟지 않게 막아주는 거야."

왜 그냥 못 바꾸나

managed state는 공유 참조를 쥐여주는데, Rust는 공유 참조로 값을 바꾸는 걸 막아. command 둘이 동시에 돌면서 데이터를 망가뜨릴 수 있으니까. 대신 안전하게 바꾸는 길이 interior mutability야. 바뀔 데이터를 Mutex<T>로 감싸는 거지. 바꾸려면 Mutex를 lock()하고, 그 락 가드가 살아 있는 동안엔 나 혼자만 만져. 락을 잡으려던 다른 command는 차례를 기다리고. 쓰다 만 값도, 레이스도 없어 — 강제로.

락은 짧게 잡고 빨리 놔

락은 가능한 한 짧게 쥐어. 잠그고, 바꾸고, 가드를 놔주고(보통 스코프가 끝나면 알아서 놔) 끝. 오래 쥔 락은 앱 전체를 그 Mutex 하나 앞에 줄 세워. 쓰기보다 읽기가 훨씬 많은 state엔 RwLock이 나아. 읽는 쪽은 여럿이 동시에 들여보내고 쓸 때만 막아주거든 — 캐시나 가끔 갱신되는 설정에 딱이야.

async 함정 (또 나왔어)

bridge 트랙의 Send 규칙 기억나? std::sync::Mutex 가드를 .await 너머까지 쥐고 있으면 안 돼. async command에선 컴파일이 안 되고, 된다 쳐도 deadlock 위험이야. await 전에 가드를 놔주거나(잠그고, 필요한 걸 복사하고, 풀고, 그다음 await) tokio::sync::Mutex처럼 async를 아는 Mutex를 써. command 대부분은 잠그고-복사하고-푸는 쪽이 제일 단순하고 빨라.

Code

command 사이에 살아남는 카운터·rust
use std::sync::Mutex;

// 가변 공유 상태: 카운터를 Mutex로 감쌈.
#[derive(Default)]
struct Counter {
    value: Mutex<i64>,
}

#[tauri::command]
fn increment(state: tauri::State<Counter>) -> i64 {
    // 락, 변경, 반환 — 가드는 줄/스코프 끝에서 떨어짐.
    let mut v = state.value.lock().unwrap();
    *v += 1;
    *v
}

pub fn run() {
    tauri::Builder::default()
        .manage(Counter::default())
        .invoke_handler(tauri::generate_handler![increment])
        .run(tauri::generate_context!())
        .expect("error");
}
잠그고, 복사하고, 풀고 — 그다음 await·rust
// async 안전 패턴: 락 안에서 복사해 내고, '그다음' await. std Mutex
// 가드를 .await 가로질러 절대 들지 마.
#[tauri::command]
async fn save_then_report(state: tauri::State<'_, Counter>) -> Result<i64, String> {
    let snapshot = { *state.value.lock().unwrap() }; // 여기서 락 떨어짐
    do_async_io(snapshot).await.map_err(|e| e.to_string())?;
    Ok(snapshot)
}

External links

Exercise

카운터를 만들어봐. Mutex<i64>를 든 managed state랑, increment랑 get_count command. 프론트엔드 버튼 둘을 엮고, 클릭 사이에도 컴포넌트가 다시 그려진 뒤에도 카운트가 남아 있고 늘어나는지 확인해 — React state가 아니라 Rust 코어에 살고 있으니까. 보너스: Mutex를 빼면 왜 컴파일이 실패하는지 한 줄로 설명해봐.
Hint
increment는 잠그고, *v += 1, *v 반환. tauri::State가 공유 참조라서 interior mutability 없이는 Rust가 변경을 안 시켜줘. 그래서 Mutex가 필요한 거야. Mutex 없이 공유 &i64에 *v += 1을 하면 컴파일 에러가 나.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.