التزامن وتعدد الخيوط في Rust: تقنيات متقدمة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

التزامن وتعدد الخيوط في Rust: مستوى متقدم

\n\n

التزامن وتعدد الخيوط من أقوى وأكثر جوانب البرمجة النظامية تعقيدًا في نفس الوقت. تقدم Rust نهجًا فريدًا: ضمانات أمان الذاكرة في مرحلة التجميع، مما يستبعد حالات السباق (data race) والمؤشرات المعلقة. في هذه المقالة، سنشرح التقنيات المتقدمة للعمل مع التزامن في Rust، بما في ذلك async/await، مكتبة Tokio، السمات Send وSync، بالإضافة إلى بدائل المزامنة منخفضة المستوى.

\n\n

المادة مخصصة للمطورين الذين هم على دراية بأساسيات Rust ويرغبون في التعمق في الحوسبة المتوازية عالية الأداء.

\n\n

1. نماذج التزامن: خيوط نظام التشغيل وعدم التزامن

\n\n

يدعم Rust نموذجين رئيسيين: خيوط نظام التشغيل (OS threads) والمهام غير المتزامنة (async tasks). يعتمد الاختيار بينهما على السيناريو: للمهام كثيفة الاستخدام لوحدة المعالجة المركزية (CPU-intensive) تكون الخيوط أفضل، بينما للمهام المرتبطة بالإدخال/الإخراج (I/O-bound) يكون async أفضل.

\n\n

خيوط نظام التشغيل (std::thread)

\n\n

إنشاء خيط في Rust أمر بسيط، ولكنه يتطلب الانتباه إلى ملكية البيانات:

\n\n
use 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>>.

\n\n

البرمجة غير المتزامنة (async/await)

\n\n

يعمل الكود غير المتزامن ضمن خيط واحد، ويتنقل بين المهام أثناء انتظار الإدخال/الإخراج. هذا يقلل من التكاليف الإضافية لتبديل السياق. مثال على دالة غير متزامنة بسيطة:

\n\n
use 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) توزع المهام بكفاءة على أنوية وحدة المعالجة المركزية.

\n\n

2. السمات Send وSync: أمان التزامن

\n\n

سمتان رئيسيتان تضمنان أمان التزامن في Rust:

\n\n
    \n
  • Send — يمكن نقل النوع بأمان بين الخيوط.
  • \n
  • Sync — يمكن الإشارة إلى النوع بأمان من عدة خيوط في وقت واحد.
  • \n
\n\n

معظم بدائل Rust تنفذ هذه السمات تلقائيًا إذا كانت مكوناتها تنفذها. على سبيل المثال، Arc<Mutex<i32>> هو Send وSync، بينما Rc<RefCell<i32>> ليس كذلك.

\n\n

لننظر إلى مثال لن يتم تجميعه بسبب انتهاك Send:

\n\n
use 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\n

3. المزامنة المتقدمة: القنوات والميوتكس وRwLock

\n\n

لتبادل البيانات بين الخيوط، تقدم Rust عدة بدائل. لننظر إلى الأكثر قوة.

\n\n

القنوات (mpsc)

\n\n

تسمح القنوات متعددة الخيوط (multi-producer, single-consumer) بإرسال الرسائل من عدة خيوط إلى مستقبل واحد:

\n\n
use 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).

المدونات

توصيات الكتب