0Pricing
Learn Rust Coding · บทเรียน

แชนเนล mpsc

ส่งข้อมูลระหว่างเธรด

แชนเนล mpsc เป็นบทเรียน Learn Rust Coding ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Learn Rust Coding และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Learn Rust Coding มีบทเรียนทั้งหมด 4 บทเรียน

Channel คืออะไร

channel คือท่อทางเดียวสำหรับส่งค่าจากเธรดหนึ่งไปยังอีกเธรดหนึ่ง ไลบรารีมาตรฐานของ Rust มี std::sync::mpsc โดย mpsc หมายถึง ผู้ผลิตหลายราย ผู้บริโภครายเดียว

  • ฝั่ง Sender ใช้ส่งค่าเข้าไป
  • ฝั่ง Receiver ใช้รับค่าออกมา

Channel ช่วยให้เธรดสื่อสารกันด้วยการ ส่งข้อความ แทนการใช้หน่วยความจำร่วมกันโดยตรง จึงหลีกเลี่ยง data race ได้หลายกรณี

สร้าง Channel

เรียก 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() จะ บล็อก จนกว่าจะมีค่ามาถึง และคืนค่า Err เมื่อ sender ทั้งหมดหายไป

  • send โอนสิทธิ์ความเป็นเจ้าของค่าเข้าไปใน channel
  • 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);
}

สิทธิ์ความเป็นเจ้าของเคลื่อนผ่าน Channel

เนื่องจาก send รับค่าไปเป็นเจ้าของ คุณจึงใช้ค่านั้นหลังส่งไม่ได้ กฎขณะคอมไพล์นี้รับประกันว่าจะไม่มีเธรดใดเก็บการอ้างอิงเก่าของข้อมูลที่อีกเธรดเป็นเจ้าของอยู่

ด้านล่าง หากพยายามพิมพ์ msg หลัง send จะเกิดข้อผิดพลาดขณะคอมไพล์ ดังนั้นเราจะใช้ค่านี้เพียงครั้งเดียว

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 implement IntoIterator การวนซ้ำผ่านมันจะคืนค่าแต่ละค่าจนกว่า channel จะปิด (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

mp ใน mpsc หมายความว่าคุณสามารถมี sender ได้หลายตัว ให้ clone Sender แล้วมอบสำเนาให้แต่ละเธรด receiver จะรวบรวมทุกค่าจนกว่า clone ทุกตัวจะถูกทิ้ง

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);
    }
}

พฤติกรรมเมื่อ Channel ปิด

ลูปรับค่าจะจบลงโดยอัตโนมัติเมื่อ sender ทั้งหมด ถูกทิ้ง หากยังมี 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 หาก channel ว่างหรือขาดการเชื่อมต่อ เหมาะสำหรับลูปเหตุการณ์ที่ต้องทำงานอื่นต่อไป

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 สามารถส่งผ่าน channel ได้ รวมถึง struct และ enum ที่คุณสร้างเอง enum เหมาะอย่างยิ่งสำหรับจำลองชนิดข้อความที่แตกต่างกันในโพรโทคอลของ worker

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) สร้าง channel แบบ จำกัดขนาด ที่มีบัฟเฟอร์ขนาด n เมื่อบัฟเฟอร์เต็ม send จะบล็อกจนกว่าจะมีพื้นที่ว่าง วิธีนี้ทำให้เกิด แรงต้านกลับ เพื่อป้องกันไม่ให้ผู้ผลิตที่เร็วส่งข้อมูลจนผู้บริโภคที่ช้ารับไม่ไหว

  • sync_channel(0) เป็น channel แบบนัดพบ: 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);
    }
}

รูปแบบ Worker อย่างง่าย

แชนเนลเหมาะอย่างยิ่งกับรูปแบบ ผู้ผลิต/ผู้บริโภค: เธรดหนึ่งสร้างรายการงานขึ้นมา ขณะที่อีกเธรดหนึ่งรับและประมวลผลรายการเหล่านั้น ในตัวอย่างนี้ เธรดหลักสร้างตัวเลข และเธรดผู้ปฏิบัติงานยกกำลังสองตัวเลขแต่ละตัว แล้วส่งผลลัพธ์กลับผ่านแชนเนลอีกช่องหนึ่ง

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” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Learn Rust Coding ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Learn Rust Coding มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “แชนเนล mpsc”

ส่งข้อมูลระหว่างเธรด คุณปฏิบัติ Learn Rust Coding ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Learn Rust Coding หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Learn Rust Coding บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “แชนเนล mpsc” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Learn Rust Coding นี้ได้ไหม

ได้ บทเรียน Learn Rust Coding ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. แชนเนล mpsc
  2. การแบ่งปันสถานะด้วย Arc/Mutex
  3. เธรดตามขอบเขต
  4. แชนเนล Crossbeam
← กลับไปที่ Learn Rust Coding