التزامن وتعدد الخيوط في Rust: من الألف إلى الياء

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

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

ابدأ الدورة

التزامن وتعدد الخيوط في Rust: من الألف إلى الياء



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



1. أساسيات تعدد الخيوط: إنشاء وإدارة الخيوط



في Rust، يتم إنشاء الخيوط باستخدام المكتبة القياسية std::thread. يتم تنفيذ كل خيط بشكل مستقل، ولكن يمكنه تبادل البيانات مع الخيوط الأخرى.



مثال بسيط لإنشاء خيط:



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(); // انتظار انتهاء الخيط}


النقاط الرئيسية:

  • thread::spawn يقبل إغلاقًا (closure) يتم تنفيذه في خيط جديد.
  • طريقة join تمنع الخيط الحالي حتى يكتمل الخيط المحدد.
  • بدون join، قد ينتهي الخيط الرئيسي قبل الخيط الفرعي.


لنقل البيانات بين الخيوط، استخدم الإغلاقات مع move:



use std::thread;

fn main() { let data = vec![1, 2, 3, 4, 5];

let handle = thread::spawn(move || { println!("البيانات من الخيط: {:?}", data); });

handle.join().unwrap(); // println!("{:?}", data); // خطأ! تم نقل البيانات بالفعل}


الكلمة المفتاحية move تنقل ملكية البيانات إلى الإغلاق، مما يمنع سباقات البيانات في مرحلة التجميع.



2. تبادل البيانات الآمن: الميوتكسات والأقفال



عندما تصل عدة خيوط إلى بيانات مشتركة، ينشأ خطر سباق البيانات. تحل Rust هذه المشكلة باستخدام الميوتكسات (Mutex) والأنواع الذرية.



مثال على استخدام 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()); // 10}


تفاصيل مهمة:

  • Arc<T> يسمح لعدة خيوط بامتلاك نفس البيانات (استنساخ ذري).
  • Mutex::lock() يعيد قفلًا؛ إذا كان القفل مشغولًا بالفعل، ينتظر الخيط.
  • يتم تحرير القفل تلقائيًا عند الخروج من النطاق.


للتحكم الدقيق، استخدم RwLock الذي يفصل بين القراء والكتّاب:



use std::sync::RwLock;

let lock = RwLock::new(5);

// القراءة: عدة خيوط في وقت واحد{ let r1 = lock.read().unwrap(); let r2 = lock.read().unwrap(); println!("نقرأ: {} و {}", r1, r2);}

// الكتابة: خيط واحد فقط{ let mut w = lock.write().unwrap(); *w += 1; println!("كتبنا: {}", w);}


3. القنوات لنقل الرسائل



بديل للذاكرة المشتركة هو نموذج الممثلين باستخدام القنوات. توفر Rust قناة متعددة المنتجين، ومستهلك واحد (mpsc).



مثال أساسي:



u                

المدونات

توصيات الكتب