ข้ามไปยังเนื้อหา

Channels

หนึ่งในวิธีที่ปลอดภัยที่สุดในการประสาน concurrency คือ message passing: แทนที่จะแชร์ memory threads จะสื่อสารโดยการส่งค่าผ่าน channel Go ทำให้ pattern นี้เป็นที่นิยมด้วย goroutines และ channels Rust มีแนวคิดเดียวกันใน standard library ผ่าน std::sync::mpsc

ใน JavaScript message passing คือ วิธีเดียว ในการสื่อสารข้ามขอบเขต Worker — postMessage ส่งสำเนาข้อมูลที่ serialize แล้ว ส่วน channel ของ Rust ต่างออกไป: โอน ownership ของค่าไปเลย ฝั่งที่ส่งจะสละสิทธิ์ในค่านั้น และ thread ที่รับกลายเป็นเจ้าของแต่เพียงผู้เดียว ไม่มีสำเนา ไม่มี lock

mpsc ย่อมาจาก multi-producer, single-consumer คุณสร้าง channel ด้วย mpsc::channel() ซึ่งคืนค่าคู่ (Sender<T>, Receiver<T>)

  • คุณสามารถ clone Sender เพื่อสร้าง producer หลายตัว (จึงเป็น “multi-producer”)
  • มี หนึ่ง Receiver เท่านั้น
  • เมื่อ Sender ทั้งหมดถูก drop channel จะปิดและ rx.recv() จะคืนค่า Err
TypeScript
// TypeScript / Node.js: Worker message passing
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
if (isMainThread) {
const worker = new Worker(__filename, { workerData: 0 });
// Messages มาถึงแบบ async ผ่าน event
worker.on('message', (msg: string) => {
console.log('received:', msg);
});
} else {
// Worker ส่ง message กลับไปยัง main thread
// ข้อมูลถูก COPY (structured clone) ไม่ใช่ transfer
parentPort?.postMessage('hello from worker');
parentPort?.postMessage('second message');
}
Rust
use std::sync::mpsc;
use std::thread;
fn main() {
// mpsc::channel() คืนค่า (Sender<T>, Receiver<T>)
let (tx, rx) = mpsc::channel::<String>();
// Spawn thread ที่ส่งสอง message แล้วออก
thread::spawn(move || {
// tx ถูก move เข้าไปใน thread — มัน own sender
tx.send(String::from("hello from thread")).unwrap();
tx.send(String::from("second message")).unwrap();
// tx ถูก drop ที่นี่ ปิด channel
});
// recv() block จนกว่าจะมี message มาถึง หรือคืน Err เมื่อปิด
for msg in rx { // rx.into_iter() drain จนกว่า channel จะปิด
println!("received: {msg}");
}
// Loop ออกอัตโนมัติเมื่อ sender ทั้งหมดถูก drop
}

ส่วน “multi-producer” ของ mpsc ให้คุณ clone Sender และกระจายไปยังหลาย thread threads ทั้งหมดเขียนเข้า receiver เดียวกัน นี่คือ pattern fan-in แบบคลาสสิก

TypeScript
// TypeScript: fan-in กับ Promise.all
async function fanIn(workers: number): Promise<string[]> {
const results = await Promise.all(
Array.from({ length: workers }, (_, i) =>
Promise.resolve(`message from worker ${i}`)
)
);
return results;
}
Rust
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel::<String>();
let workers = 3;
let mut handles = vec![];
for i in 0..workers {
let tx2 = tx.clone(); // แต่ละ thread ได้รับ Sender clone ของตัวเอง
let h = thread::spawn(move || {
tx2.send(format!("message from worker {i}")).unwrap();
});
handles.push(h);
}
// Drop sender ต้นฉบับ — เมื่อ clone ทั้งหมดถูก drop ด้วย
// channel จะปิดและ for-loop ด้านล่างจะจบ
drop(tx);
for h in handles { h.join().unwrap(); }
let mut msgs: Vec<String> = rx.iter().collect();
msgs.sort(); // sort เพื่อ output ที่แน่นอน
for m in &msgs { println!("{m}"); }
}
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel::<String>();
let workers = 3;
let mut handles = vec![];
for i in 0..workers {
let tx2 = tx.clone();
let h = thread::spawn(move || {
tx2.send(format!("message from worker {i}")).unwrap();
});
handles.push(h);
}
drop(tx);
for h in handles {
h.join().unwrap();
}
let mut msgs: Vec<String> = rx.iter().collect();
msgs.sort();
for m in &msgs {
println!("{m}");
}
}
'mpsc' ใน std::sync::mpsc ย่อมาจากอะไร?
เกิดอะไรขึ้นเมื่อ Sender clone ทั้งหมดถูก drop?
Rust channels แตกต่างจาก JS postMessage อย่างไร?