Arc และ Mutex
Shared mutable state: TypeScript vs Rust
หัวข้อที่มีชื่อว่า “Shared mutable state: TypeScript vs Rust”ใน JavaScript Workers ไม่แชร์ memory โดยปริยาย แต่ละ Worker มี heap ของตัวเอง วิธีเดียวที่จะแชร์ mutable state คือผ่าน SharedArrayBuffer ร่วมกับ Atomics ที่เป็น low-level มีโอกาสเกิด error ง่าย และแทบไม่ถูกใช้ใน application code
Rust รองรับ shared mutable state ข้าม threads ผ่านสอง type ที่ใช้ร่วมกัน:
Arc<T>— Atomically Reference Counted เป็น thread-safe smart pointer ที่ให้หลาย thread own ค่าเดียวกันบน heap เมื่อArcclone สุดท้ายถูก drop ค่าจะถูก freeMutex<T>— Mutual exclusion lock ครอบค่าTและรับประกันว่ามีแค่ thread เดียวที่เข้าถึง inner value ได้ในแต่ละขณะ
รวมกัน Arc<Mutex<T>> คือ idiom มาตรฐานของ Rust สำหรับ shared mutable state ข้าม threads
Arc — thread-safe reference counting
หัวข้อที่มีชื่อว่า “Arc — thread-safe reference counting”Arc คือคำตอบของ Rust ต่อโจทย์ “อยากมี owner หลายรายข้าม thread” หน้าตาคล้าย Rc<T> (reference counter แบบ single-thread) แต่ใช้ atomic operation กับ reference count จึงอัปเดตจากหลาย thread พร้อมกันได้อย่างปลอดภัย
คุณ clone Arc เพื่อให้ thread อื่นมี handle ไปยัง allocation เดียวกัน การ clone มีต้นทุนต่ำมาก: แค่ increment atomic integer
Mutex — interior mutability พร้อม lock
หัวข้อที่มีชื่อว่า “Mutex — interior mutability พร้อม lock”Mutex<T> ครอบค่าและป้องกันด้วย lock ในการเข้าถึง T ภายใน คุณเรียก .lock() ซึ่ง:
- Block จนกว่าไม่มี thread อื่นถือ lock
- คืนค่า
MutexGuard<T>— smart pointer ที่ dereference เป็น&mut T - Release lock อัตโนมัติเมื่อ
MutexGuardถูก drop (ปลาย scope)
นี่คือ RAII locking — pattern เดียวกับที่ Rust ใช้กับ memory: resource จะถูก release อัตโนมัติเมื่อ guard ออกจาก scope
// TypeScript: ไม่มี shared mutable state ระหว่าง Workers โดยปริยาย// SharedArrayBuffer + Atomics คือ primitive ระดับต่ำconst sab = new SharedArrayBuffer(4);const arr = new Int32Array(sab);
// Worker 1Atomics.add(arr, 0, 1); // atomic increment
// Worker 2Atomics.add(arr, 0, 1); // atomic increment
// Main threadconsole.log(Atomics.load(arr, 0)); // 2
// ในทางปฏิบัติ TS apps ส่วนใหญ่หลีกเลี่ยง shared memory ระหว่าง Workers// และใช้ message passing (postMessage) แทนuse std::sync::{Arc, Mutex};use std::thread;
fn main() { // Arc<Mutex<u32>>: shared, mutable counter ข้าม threads let counter = Arc::new(Mutex::new(0u32)); let mut handles = vec![];
for _ in 0..5 { let c = Arc::clone(&counter); // ถูก: แค่ increment atomic refcount let h = thread::spawn(move || { let mut val = c.lock().unwrap(); // acquire lock *val += 1; // mutate inner value // MutexGuard 'val' ถูก drop ที่นี่ — lock ถูก release }); handles.push(h); }
for h in handles { h.join().unwrap(); }
// มีแค่ owner เดียวเหลืออยู่ — ปลอดภัยที่จะอ่านค่าสุดท้าย println!("final counter = {}", *counter.lock().unwrap()); // 5}รวบรวมผลลัพธ์จากหลาย thread
หัวข้อที่มีชื่อว่า “รวบรวมผลลัพธ์จากหลาย thread”Pattern ที่พบบ่อยคือให้แต่ละ thread push ผลลัพธ์เข้าไปใน Vec ที่แชร์กัน แล้วอ่านผลรวมหลังจาก thread ทั้งหมด join แล้ว
// TypeScript: รวบรวมผลลัพธ์ด้วย Promise.allasync function processAll(items: number[]): Promise<number[]> { return Promise.all(items.map(item => Promise.resolve(item * item) // simulate async work ));}use std::sync::{Arc, Mutex};use std::thread;
fn main() { let results: Arc<Mutex<Vec<u32>>> = Arc::new(Mutex::new(vec![])); let items = vec![1u32, 2, 3, 4, 5]; let mut handles = vec![];
for item in items { let r = Arc::clone(&results); let h = thread::spawn(move || { r.lock().unwrap().push(item * item); }); handles.push(h); }
for h in handles { h.join().unwrap(); }
let mut res = results.lock().unwrap().clone(); res.sort(); // sort เพื่อ output ที่แน่นอน println!("squares: {:?}", res); // squares: [1, 4, 9, 16, 25]}ลองรันเลย
หัวข้อที่มีชื่อว่า “ลองรันเลย”use std::sync::{Arc, Mutex};use std::thread;
fn main() { let counter = Arc::new(Mutex::new(0u32)); let mut handles = vec![];
for _ in 0..5 { let c = Arc::clone(&counter); let h = thread::spawn(move || { let mut val = c.lock().unwrap(); *val += 1; }); handles.push(h); }
for h in handles { h.join().unwrap(); }
println!("final counter = {}", *counter.lock().unwrap());}Compiling…