مقدمة في البرمجة غير المتزامنة بلغة C++
تتطلب التطبيقات الحديثة بشكل متزايد أداءً عاليًا واستجابة سريعة. البرمجة غير المتزامنة في C++ هي أداة قوية تتيح تنفيذ مهام متعددة في وقت واحد دون حظر تيار التنفيذ الرئيسي. في هذا الدليل، سنشرح أساسيات البرمجة غير المتزامنة في C++: بدءًا من الأساليب التقليدية وصولاً إلى coroutines الحديثة المضافة في معيار C++20.
إذا كنت مبتدئًا في C++ ولكنك على دراية بقواعد اللغة الأساسية، فستساعدك هذه المقالة على فهم كيفية جعل برنامجك يقوم بعدة مهام في وقت واحد. سنستعرض أمثلة عملية يمكنك تطبيقها فورًا في مشاريعك الخاصة.
المفاهيم الأساسية ولماذا نحتاج إليها
في البرمجة المتزامنة التقليدية، يتم تنفيذ الكود بشكل تسلسلي صارم: تنتهي عملية واحدة ثم تبدأ العملية التالية. هذا بسيط ولكنه غير فعال إذا كان البرنامج ينتظر الإدخال/الإخراج (قراءة ملف، طلب شبكة) أو ينفذ عمليات حسابية ثقيلة.
تحل البرمجة غير المتزامنة هذه المشكلة من خلال السماح بـ:
- عدم حظر التيار الرئيسي — تظل واجهة التطبيق سريعة الاستجابة.
- تسريع التنفيذ — معالجة متوازية للبيانات على معالجات متعددة النوى.
- استخدام الموارد بكفاءة — بينما تنتظر مهمة ما البيانات، يمكن لمهمة أخرى أن تُنفذ.
في C++، توجد عدة طرق لتحقيق البرمجة غير المتزامنة: من الخيوط منخفضة المستوى (std::thread) إلى التجريدات عالية المستوى (std::async, std::future و coroutines).
1. البرمجة غير المتزامنة مع std::async و std::future
أبسط طريقة لتشغيل مهمة غير متزامنة في C++ هي استخدام الدالة std::async من ملف الرأس <future>. تقوم بإرجاع كائن std::future الذي يخزن نتيجة التنفيذ.
مثال أساسي
#include <iostream>#include <future>#include <thread>#include <chrono>
// الدالة التي سننفذها بشكل غير متزامنint slow_calculation(int x) { std::this_thread::sleep_for(std::chrono::seconds(2)); // محاكاة لعمل طويل return x * x;}
int main() { std::cout << "جارٍ تشغيل المهمة غير المتزامنة..." << std::endl; // تشغيل slow_calculation في خيط منفصل std::future<int> result = std::async(std::launch::async, slow_calculation, 5); std::cout << "التيار الرئيسي يواصل العمل..." << std::endl; // الحصول على النتيجة (يحظر التيار إذا لم تكن المهمة قد اكتملت بعد) int value = result.get(); std::cout << "النتيجة: " << value << std::endl; // الإخراج: 25 return 0;}في هذا المثال، تقوم std::async بتشغيل الدالة في خيط منفصل. بينما يتم تنفيذها، يقوم التيار الرئيسي بطباعة رسالة. الطريقة get() تنتظر اكتمال المهمة وتعيد النتيجة.
سياسات التشغيل
لدى std::async وضعان:
std::launch::async— يتم تنفيذ المهمة بشكل مضمون في خيط جديد.std::launch::deferred— يتم تنفيذ المهمة بشكل كسول، فقط عند استدعاءget()أوwait()في نفس التيار.
2. Promises و Futures: التحكم اليدوي
إذا كنت بحاجة إلى تحكم أكثر مرونة في المهام غير المتزامنة، استخدم الزوج std::promise و std::future. يسمح لك promise بتعيين قيمة أو استثناء في خيط واحد، بينما يسمح لك future بالحصول عليها في خيط آخر.
مثال مع promise
#include <iostream>#include <future>#include <thread>
void worker(std::promise<int> prom, int a, int b) { try { int result = a / b; // قد يحدث
```