mpsc 채널
스레드 간에 보내기
mpsc 채널은(는) CoddyKit의 무료 Learn Rust Coding 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Learn Rust Coding 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Learn Rust Coding 강의에는 총 4개의 강의가 포함되어 있습니다.
채널이란 무엇인가요?
채널은 한 스레드에서 다른 스레드로 값을 보내는 단방향 파이프입니다. Rust 표준 라이브러리는 std::sync::mpsc를 제공하며, mpsc는 여러 생산자, 하나의 소비자를 의미합니다.
- Sender 쪽은 값을 넣습니다.
- Receiver 쪽은 값을 꺼냅니다.
채널을 사용하면 메모리를 직접 공유하는 대신 메시지를 전달하여 스레드가 통신할 수 있으므로 많은 데이터 경쟁을 방지할 수 있습니다.
채널 만들기
mpsc::channel()을 호출하면 (Sender, Receiver) 튜플을 얻습니다. 여기서는 생성한 스레드에서 주 스레드로 값 하나를 보냅니다.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(42).unwrap();
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}send와 recv
tx.send(value)는 Result를 반환하며, Receiver가 삭제된 경우에만 실패합니다. rx.recv()는 값이 도착할 때까지 차단되고, 모든 Sender가 사라지면 Err를 반환합니다.
send는 값의 소유권을 채널로 이동합니다.recv는 반대편에서 소유권을 가져옵니다.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let msg = String::from("hello from thread");
tx.send(msg).unwrap();
});
let text = rx.recv().unwrap();
println!("{}", text);
}채널을 통한 소유권 이동
send는 값을 값으로 받으므로 전송한 뒤에는 해당 값을 사용할 수 없습니다. 이 컴파일 시점 규칙은 다른 스레드가 소유한 데이터에 대해 어떤 스레드도 오래된 참조를 유지하지 않도록 보장합니다.
아래에서 send 후에 msg를 출력하려 하면 컴파일 오류가 발생하므로, 한 번만 사용합니다.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let data = vec![1, 2, 3];
tx.send(data).unwrap();
// data is moved; cannot use it here
});
let v = rx.recv().unwrap();
println!("Sum: {}", v.iter().sum::<i32>());
}Receiver 반복하기
Receiver는 IntoIterator를 구현합니다. 이를 반복하면 채널이 닫힐 때까지 각 값을 얻습니다. 채널이 닫히는 시점은 모든 Sender가 삭제된 때입니다. 메시지 스트림을 소비하는 관용적인 방법입니다.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for i in 1..=3 {
tx.send(i).unwrap();
}
});
for received in rx {
println!("Received: {}", received);
}
}clone을 사용한 여러 생산자
mpsc의 mp는 여러 Sender를 사용할 수 있다는 의미입니다. Sender를 복제하고 각 스레드에 사본을 전달하세요. Receiver는 모든 복제본이 삭제될 때까지 모든 값을 수집합니다.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
thread::spawn(move || { tx.send("from A").unwrap(); });
thread::spawn(move || { tx2.send("from B").unwrap(); });
for msg in rx {
println!("{}", msg);
}
}채널 종료 의미
모든 Sender가 삭제되면 Receiver 반복문이 자동으로 끝납니다. Sender 하나라도 살아 있으면 for msg in rx는 더 많은 값을 기다리며 영원히 차단됩니다. 반복문이 끝나도록 Sender를 항상 올바르게 삭제하거나 범위를 지정하세요.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
for i in 0..3 {
tx.send(i * 10).unwrap();
}
// tx dropped here, closing the channel
});
handle.join().unwrap();
let total: i32 = rx.iter().sum();
println!("Total: {}", total);
}차단하지 않고 읽는 try_recv
recv는 차단하지만 try_recv는 Result를 즉시 반환합니다. 메시지가 준비되어 있으면 Ok(value)를, 채널이 비어 있거나 연결이 끊겼으면 Err를 반환합니다. 다른 작업도 계속해야 하는 이벤트 반복문에서 유용합니다.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
tx.send("ready").unwrap();
});
loop {
match rx.try_recv() {
Ok(msg) => { println!("{}", msg); break; }
Err(_) => println!("waiting..."),
}
thread::sleep(Duration::from_millis(20));
}
}사용자 정의 형식 보내기
Send인 형식은 무엇이든 채널을 통해 보낼 수 있으며, 사용자 정의 구조체와 열거형도 포함됩니다. 열거형은 작업자 프로토콜에서 서로 다른 메시지 종류를 모델링하는 데 적합합니다.
use std::sync::mpsc;
use std::thread;
enum Job {
Print(String),
Add(i32, i32),
}
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(Job::Print(String::from("hi"))).unwrap();
tx.send(Job::Add(2, 3)).unwrap();
});
for job in rx {
match job {
Job::Print(s) => println!("print: {}", s),
Job::Add(a, b) => println!("add: {}", a + b),
}
}
}sync_channel과 역압
mpsc::sync_channel(n)은 버퍼 크기가 n인 제한된 채널을 만듭니다. 버퍼가 가득 차면 공간이 생길 때까지 send가 차단됩니다. 이를 통해 역압이 적용되어 빠른 생산자가 느린 소비자를 압도하는 것을 방지합니다.
sync_channel(0)은 랑데부 채널입니다. send와 recv가 만나야 합니다.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::sync_channel(2);
thread::spawn(move || {
for i in 1..=4 {
tx.send(i).unwrap();
println!("sent {}", i);
}
});
for v in rx {
println!("got {}", v);
}
}간단한 작업자 패턴
채널은 생산자/소비자 패턴에서 특히 유용합니다. 한 스레드가 작업 항목을 생성하면 다른 스레드가 이를 소비하고 처리합니다. 여기서는 메인 스레드가 숫자를 생성하고, 작업 스레드가 각 숫자를 제곱한 뒤 두 번째 채널을 통해 결과를 돌려줍니다.
use std::sync::mpsc;
use std::thread;
fn main() {
let (job_tx, job_rx) = mpsc::channel();
let (res_tx, res_rx) = mpsc::channel();
thread::spawn(move || {
for n in job_rx {
res_tx.send(n * n).unwrap();
}
});
for n in 1..=4 {
job_tx.send(n).unwrap();
}
drop(job_tx);
for r in res_rx {
println!("square: {}", r);
}
}빠른 확인
mpsc 채널에 대한 이해도를 확인해 보세요.
복습
mpsc 채널의 핵심을 배웠습니다.
mpsc::channel()은(Sender, Receiver)쌍을 반환합니다.send는 값을 채널 안으로 이동시키고,recv는 값을 꺼내기 위해 대기합니다.- 수신자를 반복하면 모든 송신자가 삭제될 때까지 메시지를 소비합니다.
- 생산자가 여러 개라면
Sender를 복제합니다. try_recv는 차단되지 않으며,sync_channel(n)은 용량 제한에 따른 역압을 추가합니다.
채널을 사용하면 메모리를 공유하는 대신 소유권을 전달하여 스레드 간에 데이터를 안전하게 공유할 수 있습니다.
자주 묻는 질문
“mpsc 채널” 강의는 무료인가요?
네 — “mpsc 채널” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Learn Rust Coding 강의 전체를 잠금 해제할 수 있습니다. Learn Rust Coding 강의에는 총 4개의 강의가 포함되어 있습니다.
“mpsc 채널”에서 뭘 배우나요?
스레드 간에 보내기 브라우저에서 직접 실행하는 실습 코드로 Learn Rust Coding을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Learn Rust Coding을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Learn Rust Coding은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“mpsc 채널” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Learn Rust Coding 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Learn Rust Coding 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.