ในโลกของการพัฒนาซอฟต์แวร์สมัยใหม่ ระบบส่วนใหญ่มักต้องทำงานพร้อมกันหลายอย่าง (Concurrency) ไม่ว่าจะเป็นการจัดการคำขอจากผู้ใช้จำนวนมากบนเว็บเซิร์ฟเวอร์ หรือการจำลองสถานการณ์ที่องค์ประกอบต่างๆ ต้องอัปเดตข้อมูลของตัวเองไปพร้อมๆ กัน ปัญหาหลักที่นักพัฒนาต้องเผชิญคือ “Shared Mutable State” ซึ่งเป็นแหล่งกำเนิดของ Race Conditions และ Deadlocks หากไม่มีกลไกควบคุมที่ดีพอ การทำงานแบบขนานจะนำมาซึ่งบั๊กที่ยากต่อการค้นหาและแก้ไข
เจาะลึกรายละเอียดและประเด็นสำคัญ
ในภาษา Rust การจัดการกับ Shared State อย่างปลอดภัยนั้นอาศัยหลักการที่เรียกว่า “Ownership” และ “Borrowing” ซึ่งเป็นระบบที่ป้องกัน Data Race ได้ตั้งแต่ระดับคอมไพล์เวลา (Compile-time) เมื่อเราต้องการให้หลายเธรดเข้าถึงและแก้ไขข้อมูลเดียวกัน เราจำเป็นต้องใช้คู่หูทรงพลังสองตัวคือ Arc และ Mutex
Arc (Atomic Reference Counting) ทำหน้าที่เป็นกลไกในการนับจำนวนการอ้างอิงถึงข้อมูลอย่างปลอดภัยในสภาพแวดล้อมแบบ Multi-threaded ทำให้หลายเธรดสามารถ “เป็นเจ้าของ” ข้อมูลเดียวกันได้โดยที่ระบบยังคงรู้ว่าเมื่อใดควรปลดปล่อยหน่วยความจำนั้นออกไป ส่วน Mutex (Mutual Exclusion) ทำหน้าที่เหมือนกุญแจห้องเก็บข้อมูล มันรับประกันว่า ณ เวลาใดเวลาหนึ่ง จะมีเธรดเพียงเธรดเดียวเท่านั้นที่สามารถเข้าถึงและแก้ไขข้อมูลภายในได้ ทำให้การเปลี่ยนแปลงสถานะเป็นไปอย่างเป็นระเบียบและปราศจากการชนกันของข้อมูล
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// 1. สร้าง Shared State ที่ต้องมีการป้องกันการเข้าถึง (Mutex) และแชร์ได้หลายเธรด (Arc)
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
// Clone ทั้ง Arc และ Mutex เพื่อให้แต่ละเธรดมีสำเนาการอ้างอิงของข้อมูล
let counter_clone = Arc::clone(&counter);
let handle = thread::spawn(move || {
// 2. Lock: เข้าถึงข้อมูลภายใน Mutex อย่างปลอดภัย
let mut num = counter_clone.lock().unwrap();
*num += 1; // แก้ไขข้อมูลได้เฉพาะเมื่อถือ lock อยู่เท่านั้น
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
// ตรวจสอบผลลัพธ์สุดท้าย
println!(“Final count: {}”, *counter.lock().unwrap()); // Output: 10
}