홈강좌Rust 입문·중급 47편 중 45편째
Rust Arc 와 Mutex — 여러 스레드가 한 값을 함께 고친다
Rust 에서 여러 스레드가 한 값을 함께 가지려면 Arc 를 쓴다. Rc 를 스레드에 넘기면 E0277 로 막힌다. 함께 고치려면 Mutex 로 감싸서, lock() 으로 한 번에 한 스레드만 들어가게 한다. Arc<Mutex<T>> 로 스레드 넷이 1000번씩 더하면 언제나 4000 이 나온다.
01 Rc 는 스레드로 보낼 수 없다
39편의 Rc 로 값을 나눠 스레드에 넘겨 보자.
use std::rc::Rc;
use std::thread;
fn main() {
let shared = Rc::new(5);
let copy = Rc::clone(&shared);
let handle = thread::spawn(move || {
println!("{copy}");
});
handle.join().unwrap();
}error[E0277]: `Rc<i32>` cannot be sent between threads safely
--> src/main.rs:7:32
|
7 | let handle = thread::spawn(move || {
| ------------- ^------
| | |
| __________________|_____________within this `{closure@src/main.rs:7:32: 7:39}`
| | |
| | required by a bound introduced by this call
8 | | println!("{copy}");
9 | | });
| |_____^ `Rc<i32>` cannot be sent between threads safely
|
= help: within `{closure@src/main.rs:7:32: 7:39}`, the trait `Send` is not implemented for `Rc<i32>`- 무엇 —
Rc<i32>는 스레드 사이에 안전하게 보낼 수 없다(cannot be sent between threads safely). - 어디 — 7번째 줄의
thread::spawn에 넘긴 클로저. 그 안에Rc가 들어 있다. - 고치는 법 —
help가 이유를 말한다.Rc<i32>는Send트레이트를 갖추지 않았다.
Send 는 "다른 스레드로 보내도 안전하다" 는 트레이트(33편)다. Rc 는 주인 수를 셀 때 동시에 셀 것을 대비하지 않아서 빠르지만, 두 스레드가 동시에 세면 수가 틀어질 수 있다. 그래서 Send 가 아니고, 컴파일러가 막는다. 스레드용은 Arc(Atomic Rc)다. 쓰는 법은 Rc 와 같고, 수를 안전하게 센다.
02 Mutex — 한 번에 한 스레드만
Arc 도 Rc 처럼 안을 고칠 수 없다. 고치려면 Mutex 로 감싼다. 먼저 스레드 없이 써 보자.
use std::sync::Mutex;
fn main() {
let count = Mutex::new(0);
{
let mut n = count.lock().unwrap();
*n += 1;
}
println!("{:?}", count.lock().unwrap());
} Compiling counter v0.1.0 (~/projects/counter)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/counter`
1Mutex::new(0)은 0 을 자물쇠 안에 넣는다.count.lock()은 자물쇠를 연다. 다른 스레드가 이미 열고 있으면 닫힐 때까지 기다린다.Result를 주므로unwrap했다.- 연 결과
n으로 안의 값을 고친다(*n += 1). 39편의borrow_mut와 비슷하다. n이 블록을 나가면 자물쇠가 저절로 닫힌다. 닫는 코드를 따로 쓰지 않아서 잊을 일이 없다.
03 실습 — 스레드 넷이 함께 세기
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let count = Arc::new(Mutex::new(0));
let mut handles = Vec::new();
for _ in 0..4 {
let count = Arc::clone(&count);
handles.push(thread::spawn(move || {
for _ in 0..1000 {
let mut n = count.lock().unwrap();
*n += 1;
}
}));
}
for h in handles {
h.join().unwrap();
}
println!("count: {}", *count.lock().unwrap());
} Compiling counter v0.1.0 (~/projects/counter)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s
Running `target/debug/counter`
count: 4000Arc::new(Mutex::new(0))은 "여러 스레드가 함께 갖는, 자물쇠 달린 정수" 다.- 스레드마다
Arc::clone(&count)를 하나씩move로 가져간다. 39편의Rc::clone과 같다. - 스레드 넷이 각각 1000번
lock해서 1 을 더했다. 몇 번을 돌려도 4000 이다.
자물쇠 없이 여러 스레드가 한 값을 동시에 고치면, 같은 값을 읽고 각자 1 을 더해 쓰면서 더한 것이 사라지는 일(데이터 경쟁)이 생긴다. 다른 언어에서는 가끔씩만 틀려서 찾기 어려운 버그다. Rust 에서는 Mutex 없이 고치는 코드를 아예 컴파일할 수 없다. 1편에서 말한 "동시성 버그까지 컴파일할 때 막는다" 가 이것이다.
| 무엇을 | 한 스레드 | 여러 스레드 |
|---|---|---|
| 주인 여럿 | Rc (39편) | Arc |
| 안을 고치기 | RefCell (39편) | Mutex |
다음 편은 스레드 대신 기다리는 일을 겹쳐 처리하는 async 다.