العمل مع قوائم المهام في Rust: من البسيط إلى المعقد
قوائم المهام هي نمط أساسي في البرمجة غير المتزامنة، مما يسمح بتوزيع الموارد الحاسوبية بكفاءة بين العديد من العمليات. في Rust، لغة البرمجة النظامية التي تركز على الأمان والأداء، يتجلى تنفيذ قوائم المهام بشكل خاص بفضل نظام الملكية وأنواع البيانات وبيئات التشغيل غير المتزامنة القوية مثل tokio و async-std.
في هذه المقالة، سنشرح كيفية إنشاء وإدارة وتحسين قوائم المهام في Rust. ستتعرف على المناهج المتزامنة وغير المتزامنة، وستتعلم استخدام القنوات لنقل الرسائل، وستتعرف على أمثلة عملية يمكن تطبيقها فورًا في المشاريع.
لماذا نحتاج إلى قوائم المهام في Rust؟
في التطبيقات الحديثة، غالبًا ما تنشأ الحاجة لتنفيذ المهام بشكل غير متزامن: معالجة طلبات HTTP، الكتابة في قاعدة البيانات، إجراء حسابات خلفية. تسمح قائمة المهام بما يلي:
- فصل وضع المهمة عن تنفيذها — خيط (أو مكون) واحد يضع المهمة في القائمة، وآخر يأخذها ويعالجها.
- التحكم في الحمل — تحديد عدد المهام المنفذة في وقت واحد، وتجنب التحميل الزائد على النظام.
- تبسيط التوسع — إضافة المزيد من العمال (خيوط المعالجة) دون تغيير منطق وضع المهام.
- زيادة تحمل الأخطاء — عند فشل عامل واحد، تبقى المهمة في القائمة ويمكن معالجتها بواسطة عامل آخر.
Rust، بفضل نظام الأنواع الصارم وغياب جامع القمامة، يسمح بإنشاء قوائم مهام عالية الأداء بأقل تكاليف إضافية.
قائمة متزامنة عبر std::sync::mpsc
توفر المكتبة القياسية لـ Rust الوحدة std::sync::mpsc (Multiple Producer, Single Consumer) — قناة متعددة الخيوط لنقل الرسائل. هذه هي أبسط طريقة لتنظيم قائمة المهام.
لننظر إلى مثال: سننشئ قائمة حيث يرسل الخيط الرئيسي أرقامًا، ويقوم خيط العمل بتربيعها.
use std::sync::mpsc;use std::thread;use std::time::Duration;
fn main() { // إنشاء قناة: مرسل ومستقبل let (tx, rx) = mpsc::channel::<i32>();
// خيط عامل: يعالج المهام من القائمة let worker = thread::spawn(move || { for received in rx { println!("تم الاستلام: {}, المربع: {}", received, received * received); thread::sleep(Duration::from_millis(100)); } });
// إرسال المهام إلى القائمة for i in 1..=10 { tx.send(i).unwrap(); println!("تم الإرسال: {}", i); }
// إغلاق القناة (تلقائيًا عند سقوط tx) drop(tx);
// انتظار انتهاء العامل worker.join().unwrap();}يوضح هذا الكود النمط الأساسي: tx يرسل البيانات، rx يستقبلها في خيط منفصل. تقوم القناة بمزامنة الوصول تلقائيًا — إذا كان العامل مشغولاً، سينتظر المرسل.
قيود mpsc: مستهلك واحد فقط. للعمال المتعددين، ستحتاج إما إلى دمج القنوات أو استخدام مكتبات أكثر تقدمًا.
قائمة غير متزامنة مع Tokio و tokio::sync::mpsc
للتطبيقات غير المتزامنة (خوادم الويب، الدردشات، معالجة الأحداث) من الأفضل استخدام tokio — بيئة التشغيل غير المتزامنة الشهيرة لـ Rust. قناتها tokio::sync::mpsc تعمل مع async/await وتدعم المنتجين والمستهلكين المتعددين.
أضف إلى Cargo.toml:
[dependencies]tokio = { version = "1", features = ["full"] }مثال على قائمة غير متزامنة:
use tokio::sync::mpsc;use tokio::time::{sleep, Duration};
#[tokio::