مقدمة في قوائم المهام في Rust
\n\nقوائم المهام هي أحد الأنماط الأساسية للبرمجة المتزامنة. فهي تسمح بتنظيم نقل العمل بكفاءة بين الخيوط، مما يبسط بنية التطبيقات. في Rust، مع نظام الملكية والأنواع الصارم، يتطلب العمل مع قوائم المهام نهجًا خاصًا، لكنه يمنح في المقابل أمانًا وأداءً لا يُصدقان.
\n\nفي هذه المقالة، سنشرح كيفية إنشاء واستخدام قوائم المهام في Rust من الصفر. ستتعرف على الأدوات القياسية للغة، مثل قنوات mpsc (multi-producer, single-consumer)، وستتعرف على المكتبات الشائعة للمعالجة غير المتزامنة. المادة موجهة للمطورين المبتدئين الذين هم على دراية بأساسيات بناء جملة Rust.
الأساسيات: ما هي قائمة المهام ولماذا نحتاجها
\n\nقائمة المهام هي بنية بيانات تعمل وفق مبدأ FIFO (First In, First Out). في سياق تعدد الخيوط، تسمح لبعض الخيوط (المنتجين) بإضافة المهام، ولأخرى (المستهلكين) بأخذها وتنفيذها.
\n\nالمزايا الرئيسية لاستخدام قوائم المهام:
\n\n- \n
- فصل المسؤوليات — يتم فصل منطق إنشاء المهام عن تنفيذها. \n
- موازنة الحمل — يمكن تشغيل عدة خيوط مستهلكة للمعالجة المتوازية. \n
- التحكم في تدفق البيانات — تعمل القائمة على تخفيف الأحمال الذروة، مما يمنع إرهاق النظام. \n
- سهولة الاختبار — يمكن اختبار كل مكون بشكل منفصل. \n
في Rust، توفر المكتبة القياسية الوحدة النمطية std::sync::mpsc لإنشاء قنوات بمستقبل واحد. هذه أداة مثالية للبدء.
إنشاء قائمة مهام بسيطة باستخدام mpsc
\n\nلنبدأ بمثال أساسي: منتج واحد يرسل أرقامًا، ومستهلك واحد يعالجها.
\n\nuse std::sync::mpsc;\nuse std::thread;\nuse std::time::Duration;\n\nfn main() {\n // إنشاء قناة: tx — مرسل، rx — مستقبل\n let (tx, rx) = mpsc::channel();\n\n // تشغيل خيط منتج\n thread::spawn(move || {\n for i in 1..=5 {\n println!("جارٍ الإرسال: {}", i);\n tx.send(i).unwrap();\n thread::sleep(Duration::from_millis(500));\n }\n });\n\n // خيط مستهلك (الخيط الرئيسي)\n for received in rx {\n println!("تم الاستلام: {}", received);\n }\n}\n\n\nفي هذا المثال، يمكن استنساخ tx (المرسل) لعدة منتجين، بينما rx (المستقبل) هو المستهلك الوحيد. تقوم طريقة send بإرجاع Result، والذي نعالجه عبر unwrap() — في الكود الحقيقي، يجب معالجة الأخطاء بشكل أفضل.
منتجون متعددون
\n\nلإنشاء عدة خيوط منتجة، نقوم باستنساخ المرسل:
\n\nuse std::sync::mpsc;\nuse std::thread;\n\nfn main() {\n let (tx, rx) = mpsc::channel();\n let tx1 = tx.clone();\n\n thread::spawn(move || {\n for i in 1..=3 {\n tx1.send(format!("الخيط 1: رسالة {}", i)).unwrap();\n }\n });\n\n thread::spawn(move || {\n for i in 1..=3 {\n tx.send(format!("الخيط 2: رسالة {}", i)).unwrap();\n }\n });\n\n for received in rx {\n println!("{}", received);\n }\n}\n\n\nلاحظ: يتم نقل tx الأصلي إلى الخيط الثاني، لذلك نقوم باستنساخه قبل النقل.
تقنيات متقدمة: أنواع المهام ومعالجة الأخطاء
\n\nفي التطبيقات الحقيقية، نادرًا ما تكون المهام مجرد أرقام. غالبًا ما تكون هياكل تحتوي على بيانات ومنطق. دعنا نرى كيفية تنظيم قائمة مهام بأنواع مختلفة.
\n\nتعداد لأنواع المهام المختلفة
\n\nuse std::sync::mpsc;\nuse std::thread;\n\n#[derive(Debug)]\nenum Task {\n Compute { a: