قوائم المهام في Rust: دليل عملي مع أمثلة برمجية

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

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

ابدأ الدورة

مقدمة: لماذا قوائم المهام في Rust؟

\n\n

في تطوير الأنظمة عالية التحميل الحديثة، تُعد قوائم المهام (task queues) نمطًا أساسيًا. فهي تسمح بفصل توليد المهام عن تنفيذها، وموازنة الحمل، وتوسيع نطاق العمال (workers)، وبناء تطبيقات متسامحة مع الأخطاء. توفر Rust، مع تحكمها الصارم في الذاكرة وعدم وجود جامع للقمامة، إمكانيات فريدة لتنفيذ قوائم فعالة.

\n\n

في هذه المقالة، سنستعرض ثلاثة أساليب عملية للعمل مع قوائم المهام في Rust:

\n
    \n
  • استخدام القنوات القياسية mpsc (multi-producer, single-consumer)؛
  • \n
  • قوائم متعددة الخيوط باستخدام مكتبة crossbeam؛
  • \n
  • قوائم غير متزامنة باستخدام tokio.
  • \n
\n\n

سيصاحب كل أسلوب مثال عملي مع شروحات. ستتعلم كيفية اختيار الأداة المناسبة لمهمة محددة وتجنب الأخطاء الشائعة.

\n\n

1. القنوات الكلاسيكية: std::sync::mpsc

\n\n

توفر المكتبة القياسية لـ Rust الوحدة std::sync::mpsc — قائمة مهام متعددة الخيوط بمستهلك واحد. هذا هو الخيار المثالي عندما يكون لديك عدة منتجين (خيوط ترسل المهام) وعامل واحد (خيط ينفذها).

\n\n

مثال بسيط: تجمع من عامل واحد

\n\n
use std::sync::mpsc;\nuse std::thread;\nuse std::time::Duration;\n\nfn main() {\n    // إنشاء قناة: sender (مرسل) و receiver (مستقبل)\n    let (tx, rx) = mpsc::channel::<String>();\n\n    // تشغيل خيط العامل\n    let worker = thread::spawn(move || {\n        for task in rx {\n            println!("[عامل] أقوم بتنفيذ المهمة: {}", task);\n            thread::sleep(Duration::from_millis(500));\n            println!("[عامل] المهمة '{}' اكتملت!", task);\n        }\n    });\n\n    // إنشاء عدة منتجين (في خيط واحد للتبسيط)\n    let producer = thread::spawn(move || {\n        let tasks = vec![\n            "معالجة الملف A",\n            "إرسال بريد إلكتروني",\n            "تحديث قاعدة البيانات",\n            "إنشاء تقرير",\n        ];\n        for task in tasks {\n            println!("[منتج] أرسل: {}", task);\n            tx.send(task.to_string()).unwrap();\n            thread::sleep(Duration::from_millis(200));\n        }\n    });\n\n    producer.join().unwrap();\n    // بعد تدمير sender (tx)، سيحصل receiver (rx) على None وستنتهي الحلقة\n    drop(tx); // إغلاق القناة من جانب المنتج\n    worker.join().unwrap();\n}
\n\n

الميزات:

\n
    \n
  • القناة mpsc مخزنة مؤقتًا — المهام لا تحظر المرسل طالما أن المخزن المؤقت غير ممتلئ.
  • \n
  • مستقبل واحد فقط — هذا قيد، لكنه يضمن البساطة وأمان الخيوط.
  • \n
  • عند سقوط جميع المرسلين، تُغلق القناة تلقائيًا.
  • \n
\n\n

عمال متعددون: توزيع يدوي

\n\n

إذا كنت بحاجة إلى عدة عمال، فإن mpsc غير مناسب مباشرةً. الحل هو إنشاء قناة واحدة وعدة خيوط تتنافس على استلام المهام. ومع ذلك، يتطلب هذا الحذر: Receiver لا ينفذ Sync، لذا يجب تغليفه في Mutex أو Arc.

\n\n
use std::sync::{mpsc, Arc, Mutex};\nuse std::thread;\nuse std::time::Duration;\n\nfn main() {\n    let (tx, rx) = mpsc::channel::<String>();\n    let rx = Arc::new(Mutex::new(rx));\n\n    let mut workers = vec![];\n    for id in 0..3 {\n        let rx = Arc::clone(&rx);\n        let worker = thread::spawn(move || {\n            loop {\n                let task = {\n                    // الاستيلاء على الموتكس والحصول على المهمة\n                    let rx = rx.lock().unwrap();\n                    rx.recv()\n                };\n                match task {\n                    Ok(task) => {\n                        println!("[عامل                

المدونات

توصيات الكتب