التزامن وتعدد الخيوط في Rust: مستوى متقدم
\n\nالتزامن وتعدد الخيوط من أقوى وأكثر جوانب البرمجة النظامية تعقيدًا في نفس الوقت. تقدم Rust نهجًا فريدًا: ضمانات أمان الذاكرة في مرحلة التجميع، مما يستبعد حالات السباق (data race) والمؤشرات المعلقة. في هذه المقالة، سنشرح التقنيات المتقدمة للعمل مع التزامن في Rust، بما في ذلك async/await، مكتبة Tokio، السمات Send وSync، بالإضافة إلى بدائل المزامنة منخفضة المستوى.
\n\nالمادة مخصصة للمطورين الذين هم على دراية بأساسيات Rust ويرغبون في التعمق في الحوسبة المتوازية عالية الأداء.
\n\n1. نماذج التزامن: خيوط نظام التشغيل وعدم التزامن
\n\nيدعم Rust نموذجين رئيسيين: خيوط نظام التشغيل (OS threads) والمهام غير المتزامنة (async tasks). يعتمد الاختيار بينهما على السيناريو: للمهام كثيفة الاستخدام لوحدة المعالجة المركزية (CPU-intensive) تكون الخيوط أفضل، بينما للمهام المرتبطة بالإدخال/الإخراج (I/O-bound) يكون async أفضل.
\n\nخيوط نظام التشغيل (std::thread)
\n\nإنشاء خيط في Rust أمر بسيط، ولكنه يتطلب الانتباه إلى ملكية البيانات:
\n\nuse std::thread;\n\nlet data = vec![1, 2, 3];\n\nlet handle = thread::spawn(move || {\n println!("{:?}", data);\n});\n\nhandle.join().unwrap();\n\nالنقطة الأساسية هي الكلمة المفتاحية move. فهي تنقل الملكية إلى الإغلاق. إذا كنت بحاجة لمشاركة البيانات بين الخيوط، استخدم Arc<Mutex<T>>.
البرمجة غير المتزامنة (async/await)
\n\nيعمل الكود غير المتزامن ضمن خيط واحد، ويتنقل بين المهام أثناء انتظار الإدخال/الإخراج. هذا يقلل من التكاليف الإضافية لتبديل السياق. مثال على دالة غير متزامنة بسيطة:
\n\nuse tokio::time::{sleep, Duration};\n\n#[tokio::main]\nasync fn main() {\n let task1 = async_task("المهمة 1");\n let task2 = async_task("المهمة 2");\n\n tokio::join!(task1, task2);\n}\n\nasync fn async_task(name: &str) {\n println!("{} بدأت", name);\n sleep(Duration::from_secs(1)).await;\n println!("{} اكتملت", name);\n}\n\nمكتبة tokio هي المعيار لـ Rust غير المتزامن. توفر جدولة متعددة الخيوط (work-stealing scheduler) توزع المهام بكفاءة على أنوية وحدة المعالجة المركزية.
2. السمات Send وSync: أمان التزامن
\n\nسمتان رئيسيتان تضمنان أمان التزامن في Rust:
\n\n- \n
- Send — يمكن نقل النوع بأمان بين الخيوط. \n
- Sync — يمكن الإشارة إلى النوع بأمان من عدة خيوط في وقت واحد. \n
معظم بدائل Rust تنفذ هذه السمات تلقائيًا إذا كانت مكوناتها تنفذها. على سبيل المثال، Arc<Mutex<i32>> هو Send وSync، بينما Rc<RefCell<i32>> ليس كذلك.
لننظر إلى مثال لن يتم تجميعه بسبب انتهاك Send:
\n\nuse std::rc::Rc;\nuse std::thread;\n\nlet data = Rc::new(5);\n\nthread::spawn(move || {\n println!("{}", data);\n}); // خطأ: Rc<i32> لا ينفذ Send\n\nسيصدر مترجم Rust رسالة خطأ واضحة، مما يمنع حالة السباق في مرحلة التجميع. هذه إحدى المزايا الرئيسية للغة.
\n\n3. المزامنة المتقدمة: القنوات والميوتكس وRwLock
\n\nلتبادل البيانات بين الخيوط، تقدم Rust عدة بدائل. لننظر إلى الأكثر قوة.
\n\nالقنوات (mpsc)
\n\nتسمح القنوات متعددة الخيوط (multi-producer, single-consumer) بإرسال الرسائل من عدة خيوط إلى مستقبل واحد:
\n\nuse std::sync::mpsc;\nuse std::thread;\n\nlet (tx, rx) = mpsc::channel();\n\nfor i in 0..3 {\n let tx_clone = tx.clone();\n thread::spawn(move || {\n tx_clone.send(i).unwrap();\n });\n}\n\ndrop(tx); // نغلق المرسل\n\nfor received in rx {\n println!("تم الاستلام: {}", received);\n}\n\nالقنوات مثالية لمعمارية "المنتج-المستهلك" (producer-consumer).