قوائم المهام في C++: التنفيذ والتطبيق والتحسين

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

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

ابدأ الدورة
```html

العمل مع قوائم المهام في C++: من البسيط إلى المعقد



في التطوير الحديث بلغة C++، نادرًا ما يخلو الأمر من معالجة البيانات غير المتزامنة. قوائم المهام (task queues) هي إحدى الأدوات الرئيسية لتنظيم التنفيذ المتوازي للكود. فهي تسمح بتوزيع العمل بين الخيوط، وتجنب الحظر، والاستفادة بكفاءة من موارد المعالج. في هذه المقالة، سنشرح كيفية تنفيذ قائمة مهام بلغة C++، وسنستعرض نمط المنتج-المستهلك (producer-consumer)، ونتعلم كيفية بناء مجموعة خيوط (thread pool) بسيطة.



1. قائمة أساسية تعتمد على std::queue



أبسط قائمة مهام هي حاوية يمكن إضافة عناصر (مهام) إليها واستخراجها بترتيب FIFO. في لغة C++، تعتبر std::queue من المكتبة القياسية مناسبة تمامًا لهذا الغرض. ومع ذلك، عند العمل مع تعدد الخيوط، فإن القائمة العادية غير آمنة: فالوصول المتزامن من خيوط مختلفة يؤدي إلى سباقات البيانات (data races).



لنلقِ نظرة على مثال أساسي لقائمة متزامنة باستخدام كائن المزامنة (mutex) والمتغير الشرطي (condition variable):



#include <queue>#include <mutex>#include <condition_variable>#include <functional>

class TaskQueue {public: using Task = std::function<void()>;

void push(Task task) { std::lock_guard<std::mutex> lock(mutex_); queue_.push(std::move(task)); cond_var_.notify_one(); }

Task pop() { std::unique_lock<std::mutex> lock(mutex_); cond_var_.wait(lock, [this]() { return !queue_.empty(); }); Task task = std::move(queue_.front()); queue_.pop(); return task; }

bool empty() const { std::lock_guard<std::mutex> lock(mutex_); return queue_.empty(); }

private: std::queue<Task> queue_; mutable std::mutex mutex_; std::condition_variable cond_var_;};


في هذا الكود، تقوم push() بإضافة مهمة وإخطار خيط منتظر واحد. بينما تقوم pop() بحظر الخيط إلى أن تصبح القائمة غير فارغة. هذا الأسلوب يحل مشكلة المزامنة، لكنه لا يتوسع ليشمل العديد من المستهلكين دون تحسينات.



2. نمط المنتج-المستهلك (Producer-Consumer)



المشكلة الكلاسيكية للمنتج-المستهلك (producer-consumer) تنطبق تمامًا على قائمة المهام. يقوم خيط أو أكثر من خيوط المنتجين بتوليد المهام، بينما تقوم خيوط المستهلكين بتنفيذها. من المهم إدارة حجم القائمة بشكل صحيح لتجنب الامتلاء الزائد أو التوقف عن العمل.



مثال مع عدة مستهلكين:



#include <thread>#include <vector>#include <iostream>

void worker(TaskQueue& queue, int id) { while (true) { auto task = queue.pop(); if (!task) break; // إشارة إنهاء std::cout << "Worker " << id << " executing task"; task(); }}

int main() { TaskQueue queue; std::vector<std::thread> workers;

// تشغيل 4 خيوط مستهلكة for (int i = 0; i < 4; ++i) { workers.emplace_back(worker, std::ref(queue), i); }

// المنتج يضيف المهام for (int i = 0; i < 10; ++i) { queue.push([i]() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "Task " << i << " done"; }); }

// إشارة إنهاء (مهمة nullptr) for (auto& w : workers) { queue.push(nullptr); }

for (auto& w : workers) { w.join(); } return 0;}


هنا، يحصل كل مستهلك على مهمة، وينفذها، ثم ينتظر المهمة التالية. إشارة الإنهاء (nullptr) توقف الحلقة. غالبًا ما يُستخدم هذا النمط في تطبيقات الخوادم، ومعالجات الأحداث، ومحركات الألعاب.



3. مجموعة الخيوط (Thread Pool) — تجمع خيوط لقائمة المهام



مجموعة الخيوط (thread pool) هي طبقة عليا فوق قائمة المهام، وتقوم بإدارة ```

المدونات

توصيات الكتب