Channels
Message passing ใน TypeScript vs Rust
หัวข้อที่มีชื่อว่า “Message passing ใน TypeScript vs Rust”หนึ่งในวิธีที่ปลอดภัยที่สุดในการประสาน 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
std::sync::mpsc
หัวข้อที่มีชื่อว่า “std::sync::mpsc”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 / Node.js: Worker message passingimport { 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');}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}หลาย producer (fan-in pattern)
หัวข้อที่มีชื่อว่า “หลาย producer (fan-in pattern)”ส่วน “multi-producer” ของ mpsc ให้คุณ clone Sender และกระจายไปยังหลาย thread threads ทั้งหมดเขียนเข้า receiver เดียวกัน นี่คือ pattern fan-in แบบคลาสสิก
// TypeScript: fan-in กับ Promise.allasync function fanIn(workers: number): Promise<string[]> { const results = await Promise.all( Array.from({ length: workers }, (_, i) => Promise.resolve(`message from worker ${i}`) ) ); return results;}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}"); }}Compiling…