مخطط المهام بلغة C++: الهندسة المعمارية والتنفيذ والأمثلة
مخطط المهام (Task Scheduler) هو أحد المكونات الرئيسية للأنظمة عالية التحميل الحديثة، ومحركات الألعاب، وخوادم التطبيقات، والأجهزة المضمنة. في هذه المقالة، سنقوم بتحليل مفصل لكيفية إنشاء مخطط مهام خاص بك بلغة C++ من الصفر، باستخدام المكتبة القياسية للغة. ستتعرف على كيفية عمل قائمة انتظار المهام، وكيف تعمل المؤقتات، وكيفية مزامنة الخيوط وتجنب الأخطاء النموذجية.
لماذا نحتاج إلى مخطط المهام؟
يسمح مخطط المهام بما يلي:
- تنفيذ الكود بشكل غير متزامن في خيوط الخلفية؛
- تعيين تأخيرات وتشغيلات دورية؛
- التحكم في الأولويات وترتيب التنفيذ؛
- تحسين استخدام موارد وحدة المعالجة المركزية (CPU).
على عكس std::thread البسيط، يوفر مخطط المهام تحكمًا مرنًا في التنفيذ ويسمح بتجنب إنشاء خيوط لكل مهمة.
الهندسة المعمارية الأساسية
يتكون أي مخطط من ثلاثة أجزاء رئيسية:
- قائمة انتظار المهام — تخزن كائنات المهام (الدوال، الوسائط، البيانات الوصفية)؛
- مجموعة الخيوط — خيط أو أكثر من خيوط العمل التي تسحب المهام من قائمة الانتظار وتنفذها؛
- المؤقت / مخطط الوقت — يسمح بتأخير التنفيذ أو تشغيل المهام وفقًا لجدول زمني.
تنفيذ قائمة انتظار المهام بلغة C++
في البداية، سنحدد بنية المهمة:
struct Task { std::function<void()> func; std::chrono::steady_clock::time_point executeAt; size_t id;};يجب أن تكون قائمة الانتظار آمنة للخيوط. سنستخدم std::priority_queue (بحيث يتم استخراج المهام ذات وقت التنفيذ الأقل أولاً) و std::mutex للمزامنة:
#include <queue>#include <mutex>#include <condition_variable>#include <functional>#include <chrono>#include <thread>#include <vector>
class TaskScheduler {public: explicit TaskScheduler(size_t threadCount = std::thread::hardware_concurrency()) { for (size_t i = 0; i < threadCount; ++i) { workers.emplace_back([this] { workerLoop(); }); } }
~TaskScheduler() { stop(); }
// إضافة مهمة مع تأخير template<typename F, typename... Args> void schedule(F&& f, Args&&... args, std::chrono::milliseconds delay = std::chrono::milliseconds(0)) { auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)...); Task t{std::move(task), std::chrono::steady_clock::now() + delay, nextId++}; { std::lock_guard<std::mutex> lock(mutex); queue.push(std::move(t)); } cv.notify_one(); }
void stop() { { std::lock_guard<std::mutex> lock(mutex); stopping = true; } cv.notify_all(); for (auto& worker : workers) { if (worker.joinable()) worker.join(); } }
private: struct Task { std::function<void()> func; std::chrono::steady_clock::time_point executeAt; size_t id;
bool operator>(const Task& other) const { return executeAt > other.executeAt; } };
void workerLoop() { while (true) { std::unique_lock<std::mutex> lock(mutex); cv.wait(lock, [this] { return stopping || !queue.empty(); }); if (stopping && queue.empty()) return;
if (!queue.empty()) { auto top = queue.top(); if (std::chrono::steady_clock::now() >= top.executeAt) { queue.pop(); lock.unlock(); top.func