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

การแบ่งปันสถานะด้วย Arc/Mutex

ข้อมูลร่วมที่ปลอดภัย

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

เหตุใดสถานะที่ใช้ร่วมกันจึงจัดการได้ยาก

บางครั้งการส่งข้อความก็ไม่เพียงพอ และมีหลายเธรดที่จำเป็นต้องอ่านและเขียนข้อมูลชุดเดียวกันจริง ๆ Rust จะไม่อนุญาตให้คุณแบ่งปันค่าที่เปลี่ยนแปลงได้ระหว่างเธรดโดยไม่มีการป้องกัน เพราะนั่นอาจทำให้เกิดการแข่งขันของข้อมูล

เครื่องมือสองอย่างที่ใช้ร่วมกันคือ:

  • Arc สำหรับแบ่งปันความเป็นเจ้าของระหว่างเธรด
  • Mutex สำหรับการแก้ไขข้อมูลแบบผูกขาดอย่างปลอดภัย

Rc ไม่ปลอดภัยสำหรับเธรด

Rc ช่วยให้มีความเป็นเจ้าของร่วมกันได้ แต่ใช้ได้กับเธรดเดียวเท่านั้น ตัวนับการอ้างอิงของมันไม่ได้รับการทำให้สอดคล้องกัน ดังนั้นคอมไพเลอร์จึงไม่ยอมให้ส่งมันข้ามเธรด หากต้องการแบ่งปันข้อมูลแบบหลายเธรด คุณต้องใช้ Arc (การนับการอ้างอิงแบบอะตอมิก)

Arc ทำงานคล้าย Rc แต่ปรับปรุงตัวนับด้วยการดำเนินการแบบอะตอมิก ทำให้การโคลนปลอดภัยระหว่างเธรด

use std::sync::Arc;

fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    let clone1 = Arc::clone(&data);
    println!("original: {:?}", data);
    println!("clone:    {:?}", clone1);
    println!("count:    {}", Arc::strong_count(&data));
}

Mutex ให้สิทธิ์เข้าถึงแบบผูกขาด

Mutex ห่อหุ้มข้อมูลและรับประกันว่าจะมีเธรดเพียงหนึ่งเธรดเข้าถึงข้อมูลนั้นในแต่ละครั้ง คุณเรียก .lock() เพื่อรับ MutexGuard ซึ่งทำหน้าที่อ้างอิงไปยังค่าภายใน ล็อกจะถูกปล่อยโดยอัตโนมัติเมื่อการ์ดออกจากขอบเขต

use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);
    {
        let mut num = m.lock().unwrap();
        *num += 10;
    } // guard dropped here, lock released
    println!("value = {:?}", m.lock().unwrap());
}

การใช้ Arc และ Mutex ร่วมกัน

หากต้องการแบ่งปันข้อมูลที่เปลี่ยนแปลงได้ระหว่างเธรด ให้ห่อข้อมูลในรูปแบบ Arc>:

  • Arc ช่วยให้หลายเธรดถือแฮนเดิลไปยังข้อมูลชุดเดียวกันได้
  • Mutex ช่วยให้แต่ละเธรดแก้ไขข้อมูลได้อย่างปลอดภัยทีละเธรด

โคลน Arc สำหรับแต่ละเธรดก่อนสร้างเธรด

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let c = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut n = c.lock().unwrap();
        *n += 1;
    });
    handle.join().unwrap();
    println!("counter = {}", *counter.lock().unwrap());
}

ตัวนับร่วมกันระหว่างหลายเธรด

ตัวอย่างคลาสสิกคือ เธรดสิบเธรดเพิ่มค่าตัวนับที่ใช้ร่วมกันทีละหนึ่ง ทุกเธรดถือสำเนา Arc ของตนเอง และล็อก Mutex เพื่อเพิ่มค่า หลังจากรอให้ทุกเธรดทำงานเสร็จ ยอดรวมจะเท่ากับ 10 พอดีโดยไม่มีการแข่งขันของข้อมูล

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    for _ in 0..10 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            let mut num = c.lock().unwrap();
            *num += 1;
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
    println!("Result: {}", *counter.lock().unwrap());
}

ขอบเขตของล็อกมีความสำคัญ

MutexGuard จะถือล็อกไว้จนกว่าจะถูกทำลาย หากถือมันไว้ระหว่างการทำงานที่ช้า เธรดอื่นจะถูกขัดขวาง ควรทำส่วนวิกฤตให้สั้น: ล็อก แก้ไข แล้วปล่อย การครอบการล็อกด้วยบล็อกขนาดเล็กจะช่วยให้ล็อกถูกปล่อยก่อนเริ่มการประมวลผลเพิ่มเติม

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let log = Arc::new(Mutex::new(Vec::new()));
    let mut handles = vec![];
    for i in 0..3 {
        let l = Arc::clone(&log);
        handles.push(thread::spawn(move || {
            {
                let mut v = l.lock().unwrap();
                v.push(i);
            } // released quickly
        }));
    }
    for h in handles { h.join().unwrap(); }
    let mut result = log.lock().unwrap().clone();
    result.sort();
    println!("{:?}", result);
}

ภาวะชะงักงัน: ความเสี่ยงที่เกิดขึ้นจริง

ภาวะชะงักงันเกิดขึ้นเมื่อเธรดสองเธรดต่างถือล็อกที่อีกเธรดหนึ่งต้องการ และทั้งคู่รอคอยตลอดไป Rust ป้องกันการแข่งขันของข้อมูลได้ แต่ไม่ได้ป้องกันภาวะชะงักงัน หลีกเลี่ยงปัญหานี้โดยล็อก Mutex หลายตัวตามลำดับเดียวกันเสมอ และถือเครื่องหมายล็อกไว้เป็นเวลาสั้น ๆ

นอกจากนี้ หลีกเลี่ยงการล็อก Mutex ตัวเดิมสองครั้งในเธรดเดียว เพราะ Mutex มาตรฐานไม่รองรับการล็อกซ้ำโดยเธรดเดิม

การทำให้ล็อกเสียเมื่อเธรดเกิดภาวะตื่นตระหนก

หากเธรดเกิดภาวะตื่นตระหนกขณะถือล็อก Mutex จะถูกทำให้เสีย การเรียก .lock() ในภายหลังจะคืนค่า Err เพื่อแจ้งให้ทราบว่าข้อมูลอาจไม่สอดคล้องกัน หากคุณตัดสินใจว่าข้อมูลยังใช้งานได้ คุณสามารถกู้คืนการ์ดภายในด้วย into_inner() จากข้อผิดพลาดนั้น

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let data = Arc::new(Mutex::new(0));
    let d = Arc::clone(&data);
    let _ = thread::spawn(move || {
        let mut g = d.lock().unwrap();
        *g = 7;
        panic!("boom"); // poisons the mutex
    }).join();
    match data.lock() {
        Ok(g) => println!("ok: {}", *g),
        Err(poisoned) => println!("recovered: {}", *poisoned.into_inner()),
    }
}

RwLock สำหรับผู้อ่านจำนวนมาก

เมื่อการอ่านมีจำนวนมากกว่าการเขียนอย่างชัดเจน RwLock อาจทำงานได้เร็วกว่า Mutex โดยอนุญาตให้มีผู้อ่านหลายรายพร้อมกันหรือผู้เขียนเพียงหนึ่งราย ใช้ .read() สำหรับการเข้าถึงร่วมกัน และใช้ .write() สำหรับการเข้าถึงแบบผูกขาด

use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let config = Arc::new(RwLock::new(String::from("v1")));
    let reader = Arc::clone(&config);
    let r = thread::spawn(move || {
        let val = reader.read().unwrap();
        println!("read: {}", *val);
    });
    r.join().unwrap();
    {
        let mut w = config.write().unwrap();
        *w = String::from("v2");
    }
    println!("final: {}", *config.read().unwrap());
}

อะตอมิกสำหรับตัวนับอย่างง่าย

สำหรับตัวนับจำนวนเต็มเพียงตัวเดียว การใช้ Mutex เต็มรูปแบบถือว่าเกินความจำเป็น ชนิดข้อมูลอย่าง AtomicUsize มีการอัปเดตแบบไม่ใช้ล็อกผ่านเมธอด เช่น fetch_add ห่อชนิดข้อมูลเหล่านี้ด้วย Arc เพื่อแบ่งปันระหว่างเธรด เลือก Ordering ให้เหมาะสม โดย SeqCst เป็นค่าเริ่มต้นที่ปลอดภัยและเข้าใจง่ายที่สุด

use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;

fn main() {
    let counter = Arc::new(AtomicUsize::new(0));
    let mut handles = vec![];
    for _ in 0..5 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            c.fetch_add(1, Ordering::SeqCst);
        }));
    }
    for h in handles { h.join().unwrap(); }
    println!("count = {}", counter.load(Ordering::SeqCst));
}

การเลือกเครื่องมือที่เหมาะสม

แนวทางอย่างรวดเร็วสำหรับสถานะที่ใช้ร่วมกัน:

  • Channel: ถ่ายโอนความเป็นเจ้าของในลักษณะไปป์ไลน์
  • Arc<Mutex>: โครงสร้างที่เปลี่ยนแปลงได้และใช้ร่วมกัน โดยมีทั้งการอ่านและการเขียน
  • Arc<RwLock>: ข้อมูลที่ใช้ร่วมกันและเน้นการอ่าน
  • Atomics: ตัวนับหรือแฟล็กพื้นฐานแบบเดี่ยว

ควรเลือกเครื่องมือที่เรียบง่ายที่สุดและตรงกับงาน ใช้ล็อกเมื่อการส่งข้อความไม่สามารถจำลองปัญหาได้ดี

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจของคุณเกี่ยวกับสถานะที่ใช้ร่วมกัน

ทบทวน

คุณได้เรียนรู้วิธีแบ่งปันสถานะระหว่างเธรดอย่างปลอดภัย:

  • Arc ทำให้แบ่งปันความเป็นเจ้าของระหว่างเธรดได้อย่างปลอดภัย ส่วน Rc ทำไม่ได้
  • Mutex ให้สิทธิ์แก้ไขแบบผูกขาดผ่านการ์ดที่ปลดล็อกโดยอัตโนมัติ
  • Arc<Mutex<T>> เป็นรูปแบบมาตรฐานสำหรับข้อมูลที่เปลี่ยนแปลงได้และใช้ร่วมกัน
  • ทำขอบเขตของล็อกให้สั้น และระวังภาวะชะงักงันกับการทำให้ล็อกเสีย
  • RwLock เหมาะกับข้อมูลที่เน้นการอ่าน ส่วนอะตอมิกเหมาะกับตัวนับอย่างง่าย

คำถามที่พบบ่อย

บทเรียน “การแบ่งปันสถานะด้วย Arc/Mutex” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การแบ่งปันสถานะด้วย Arc/Mutex” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Learn Rust Coding ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Learn Rust Coding มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การแบ่งปันสถานะด้วย Arc/Mutex”

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

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

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

บทเรียน “การแบ่งปันสถานะด้วย Arc/Mutex” ใช้เวลานานแค่ไหน

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

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

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

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

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