التزامن وتعدد الخيوط في Rust: البرمجة الآمنة

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

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

ابدأ الدورة

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



Rust هي واحدة من اللغات القليلة التي تحل مشكلة سباقات البيانات (data races) في مرحلة التجميع. بفضل نظام الملكية (ownership) والاقتراض (borrowing)، يصبح الكود المتزامن في Rust ليس سريعًا فحسب، بل آمنًا أيضًا. في هذه المقالة، سنشرح أساسيات تعدد الخيوط في Rust: إنشاء الخيوط، تبادل البيانات عبر القنوات والميوتكسات، بالإضافة إلى التعرف على نموذج "Fearless Concurrency".



1. أساسيات تعدد الخيوط: std::thread



يوفر Rust دعمًا مدمجًا للخيوط من خلال الوحدة std::thread. يمكن إنشاء خيط جديد باستخدام الدالة thread::spawn، والتي تأخذ إغلاقًا (closure).



use std::thread;use std::time::Duration;

fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("الخيط: {}", i); thread::sleep(Duration::from_millis(1)); } });

for i in 1..5 { println!("الخيط الرئيسي: {}", i); thread::sleep(Duration::from_millis(1)); }

handle.join().unwrap(); // انتظار انتهاء الخيط}


تقوم طريقة join() بحظر الخيط الرئيسي حتى ينتهي الخيط الفرعي. بدونها، قد ينتهي البرنامج قبل أن يتم تنفيذ الخيط.



2. نقل البيانات بين الخيوط: القنوات (mpsc)



لتبادل الرسائل بين الخيوط في Rust، يُستخدم نموذج قناة متعددة المنتجين، مستهلك واحد (mpsc — multiple producer, single consumer). هذا مُنفَّذ في الوحدة std::sync::mpsc.



use std::sync::mpsc;use std::thread;

fn main() { let (tx, rx) = mpsc::channel();

thread::spawn(move || { let val = String::from("مرحبًا من الخيط!"); tx.send(val).unwrap(); // تم نقل val بالفعل، لا يمكن استخدامه });

let received = rx.recv().unwrap(); println!("تم الاستلام: {}", received);}


لاحظ الكلمة المفتاحية move في الإغلاق — فهي تنقل ملكية المتغير tx إلى الخيط الجديد. ترجع طريقة send() قيمة Result، والتي قد تكون Err إذا كان المستقبل مغلقًا.



لإنشاء عدة منتجين، يمكن استنساخ tx:



let (tx, rx) = mpsc::channel();let tx1 = tx.clone();

thread::spawn(move || { tx.send("الرسالة 1").unwrap();});

thread::spawn(move || { tx1.send("الرسالة 2").unwrap();});

for received in rx { println!("تم الاستلام: {}", received);}


3. الذاكرة المشتركة: Mutex و Arc



القنوات مفيدة لنقل البيانات، لكن في بعض الأحيان نحتاج لمشاركة الحالة بين الخيوط. لهذا يُستخدم Mutex (الاستبعاد المتبادل) مع Arc (عداد المرجع الذري).



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 counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); }

for handle in handles { handle.join().unwrap(); }

println!("النتيجة: {}", *counter.lock().unwrap());}


لماذا Arc وليس Rc؟ Rc العادي ليس آمنًا للخيوط، لأن عداد المراجع الخاص به ليس ذريًا. Arc يستخدم عمليات ذرية، مما يجعله آمنًا لتعدد الخيوط.



مهم: Mutex يحظر الخيط عند استدعاء lock(). بعد الخروج من نطاق الرؤية، يتم تحرير القفل تلقائيًا (بفضل trait Drop).



4. نموذج Fearless Concurrency و Send/Sy

المدونات

توصيات الكتب