قوائم الانتظار والمهام الخلفية في لغة C++: كتابة البرمجة غير المتزامنة دون ألم

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

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

ابدأ الدورة

لماذا يتباطأ برنامجك وكيف تصلح ذلك

\n\n

تخيل: لقد كتبت محللاً رائعاً بلغة C++ يجب أن يعالج 10,000 عنوان URL. تقوم بتشغيله، وتذهب لتحضر القهوة، وتعود - ولا يزال يقوم بتحميل الموقع الأول. هل هذا مألوف؟ المشكلة هي أن البرنامج يعمل بشكل متزامن: بينما يتم تنفيذ طلب واحد، تنتظر الطلبات الأخرى دورها، كما في متجر سوفييتي.

\n\n

في العالم الحديث، لا يغفر المستخدمون التأخير. إذا استجاب الخادم بأكثر من 200 مللي ثانية - فأنت تفقد العملاء. ولكن كيف تجعل لغة C++ تعمل بشكل أسرع دون إعادة كتابة كل شيء من الصفر؟ الإجابة هي قوائم الانتظار والمهام الخلفية. هذا ليس مجرد مصطلح عصري، بل هو نمط أساسي يُستخدم في نواة لينكس ومحركات الألعاب والخدمات عالية التحميل.

\n\n

في هذه المقالة، سنفهم كيفية عمل قوائم انتظار المهام في لغة C++، ونكتب تجمع خيوط بسيط خاص بنا، ونتعلم كيفية معالجة البيانات في الخلفية دون سباقات البيانات أو حالات الجمود. هل أنت مستعد لتحسين كودك؟ لنبدأ.

\n\n

ما هي قائمة انتظار المهام ولماذا هي ضرورية

\n\n

قائمة انتظار المهام (task queue) هي بنية بيانات تعمل وفق مبدأ FIFO (أول من يدخل، أول من يخرج). تخيل خط تجميع: تضيف المهام من طرف، وتأخذها خيوط التنفيذ من الطرف الآخر. هذا هو أساس نمط المنتج-المستهلك (Producer-Consumer).

\n\n

لماذا هذا ضروري في لغة C++؟ إليك ثلاثة أسباب رئيسية:

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

على سبيل المثال، يمكن لخادم ويب بلغة C++ وضع طلبات HTTP الواردة في قائمة انتظار، بينما يقوم تجمع الخيوط بمعالجتها بالتوازي. بينما يقوم خيط بتحليل JSON، يكتب آخر في قاعدة البيانات - وكل هذا دون حظر الحلقة الرئيسية.

\n\n

كتابة قائمة انتظار بسيطة بلغة C++ من الصفر

\n\n

لنبدأ بمثال أساسي. في المكتبة القياسية للغة C++ يوجد std::queue، ولكن لتعدد الخيوط يجب تغليفها في فئة محمية. إليك أبسط تطبيق لقائمة انتظار آمنة للخيوط:

\n\n
#include <queue>\n#include <mutex>\n#include <condition_variable>\n#include <functional>\n#include <thread>\n\nclass ThreadSafeQueue {\nprivate:\n    std::queue<std::function<void()>> tasks;\n    std::mutex mtx;\n    std::condition_variable cv;\n\npublic:\n    void push(std::function<void()> task) {\n        std::lock_guard<std::mutex> lock(mtx);\n        tasks.push(task);\n        cv.notify_one();  // نوقظ خيطاً واحداً منتظراً\n    }\n\n    std::function<void()> pop() {\n        std::unique_lock<std::mutex> lock(mtx);\n        cv.wait(lock, [this] { return !tasks.empty(); });\n        auto task = tasks.front();\n        tasks.pop();\n        return task;\n    }\n};\n
\n\n

هنا نستخدم std::mutex لحماية الوصول إلى قائمة الانتظار و std::condition_variable حتى لا تستهلك الخيوط المعالج بلا فائدة أثناء انتظار مهام جديدة. عندما تظهر مهمة، نوقظ خيطاً واحداً باستخدام notify_one().

\n\n

ولكن هذا فقط نصف العمل. نحتاج إلى تجمع خيوط (thread pool) لتنفيذ هذه المهام.

\n\n

إنشاء تجمع خيوط: قلب المهام الخلفية

\n\n

تجمع الخيوط هو مجموعة من الخيوط المنشأة مسبقاً والتي تنتظر العمل. بدلاً من إنشاء وتدمير الخيوط لكل مهمة (وهو أمر مكلف)، نقوم بإعادة استخدامها. إليك تطبيق تقليدي:

\n\n
class ThreadPool {\nprivate:\n    std::vector<std::thread> workers;\n    ThreadSafeQueue queue;\n    bool stop = false;\n\n    void worker_loop() {\n        while (!stop) {\n                

المدونات

توصيات الكتب