홈강좌Rust 입문·중급 47편 중 44편째
Rust 채널 — 스레드끼리 값을 보내고 받는다
Rust 의 mpsc::channel() 은 보내는 쪽(tx)과 받는 쪽(rx)을 만든다. 스레드는 tx.send 로 값을 보내고, 받는 쪽은 rx 를 for 로 돌며 받는다. 보낸 값의 주인은 받는 쪽으로 넘어가서, 보낸 뒤에 쓰면 E0382 가 난다. tx.clone() 으로 보내는 쪽을 여럿 둘 수 있다.
01 보내고 받는다
작업 이름 셋을 한 스레드가 보내고, main 이 받는다.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for job in ["download", "resize", "upload"] {
tx.send(job).unwrap();
}
});
for job in rx {
println!("got: {job}");
}
println!("channel closed");
} Compiling channel v0.1.0 (~/projects/channel)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.29s
Running `target/debug/channel`
got: download
got: resize
got: upload
channel closedmpsc::channel()은(보내는 쪽, 받는 쪽)튜플을 준다.let (tx, rx)로 풀었다. mpsc 는 "보내는 쪽 여럿, 받는 쪽 하나(multiple producer, single consumer)" 의 줄임이다.- 스레드는
tx를move로 가져가서(43편)tx.send(값)으로 보낸다. 받는 쪽이 사라졌으면send가Err를 주므로unwrap했다. for job in rx는 값이 올 때마다 하나씩 받는다. 아직 안 왔으면 올 때까지 기다린다.- 스레드가 끝나면 그 안의
tx도 치워진다. 보내는 쪽이 하나도 남지 않으면 채널이 닫히고,for가 끝나channel closed가 찍혔다.
43편처럼 결과를 join 으로 받으면 스레드가 끝나야 한 번에 받는다. 채널은 일이 진행되는 동안 값을 하나씩 흘려보낸다.
02 보낸 값은 받는 쪽의 것이다
보낸 문자열을 보낸 뒤에 찍어 보자.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let report = String::from("done");
thread::spawn(move || {
tx.send(report).unwrap();
println!("sent {report}");
});
println!("{}", rx.recv().unwrap());
}error[E0382]: borrow of moved value: `report`
--> src/main.rs:10:25
|
9 | tx.send(report).unwrap();
| ------ value moved here
10 | println!("sent {report}");
| ^^^^^^ value borrowed here after move
|
= note: move occurs because `report` has type `String`, which does not implement the `Copy` trait15편과 같은 E0382 다. 9번째 줄 tx.send(report) 에서 값이 옮겨 갔고(value moved here), 10번째 줄에서 그 뒤에 썼다. send 는 값의 주인을 받는 쪽으로 넘긴다. 받는 쪽이 그 값을 고치는 동안 보낸 쪽이 읽으면 안 되니, 소유권 규칙이 스레드 사이에서도 그대로 지켜진다. 찍는 줄을 send 앞으로 옮기면 된다.
03 실습 — 일꾼 셋이 한 채널로 보내기
보내는 쪽은 clone 으로 여럿 만들 수 있다.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
for id in 1..=3 {
let tx = tx.clone();
thread::spawn(move || {
let result = id * 10;
tx.send((id, result)).unwrap();
});
}
drop(tx);
let mut results: Vec<(i32, i32)> = rx.iter().collect();
results.sort();
println!("{results:?}");
} Compiling channel v0.1.0 (~/projects/channel)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/channel`
[(1, 10), (2, 20), (3, 30)]- 스레드마다
tx.clone()을 하나씩 가져간다. 모두 같은rx로 보낸다. drop(tx)로 원래의tx를 치웠다. 이것을 빼면main이tx를 계속 갖고 있어서 채널이 닫히지 않고, 받는 쪽이 영원히 기다린다.rx.iter().collect()는 채널이 닫힐 때까지 받은 값을 모은다(37편의 반복자).- 세 스레드가 보내는 순서는 실행마다 다를 수 있다. 그래서
sort()로 정렬해서 찍었다.
drop(tx); 줄을 지우고 돌려 보자. 프로그램이 끝나지 않는다(Ctrl + C 로 멈춘다). 채널 말고 여러 스레드가 한 값을 함께 고치는 방법도 있다. 다음 편의 Arc 와 Mutex 다.