التزامن وتعدد الخيوط في C++: المستوى المتقدم

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

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

ابدأ الدورة

التزامن وتعدد الخيوط في C++: من الأساسيات إلى التقنيات المتقدمة



تتطلب التطبيقات الحديثة بشكل متزايد أقصى أداء، خاصة عند العمل مع المعالجات متعددة النوى. تقدم لغة C++ أدوات قوية للعمل مع التزامن وتعدد الخيوط، بدءًا من C++11 وحتى أحدث المعايير. في هذه المقالة، سنقوم بتحليل الجوانب المتقدمة لتعدد الخيوط: من الاستخدام الصحيح لكائنات المزامنة والعمليات الذرية إلى أنماط المزامنة المعقدة. المادة موجهة للمطورين الذين هم على دراية بالفعل بأساسيات الخيوط في C++.



1. إدارة الخيوط: std::thread و std::jthread



الفئة std::thread هي أساس البرمجة متعددة الخيوط في C++. ومع ذلك، على المستوى المتقدم، من المهم ليس فقط إنشاء خيط، ولكن أيضًا إدارة دورة حياته بشكل صحيح. مع C++20، ظهر std::jthread، الذي ينضم تلقائيًا عند التدمير، مما يمنع تسرب الموارد.



مثال على استخدام std::jthread:



#include <iostream>#include <thread>#include <chrono>

void worker(int id) { std::this_thread::sleep_for(std::chrono::milliseconds(100 * id)); std::cout << "الخيط " << id << " اكتمل";}

int main() { // std::jthread ينضم تلقائيًا عند الخروج من النطاق std::jthread t1(worker, 1); std::jthread t2(worker, 2); std::jthread t3(worker, 3); // لا حاجة لاستدعاء join() أو detach() بشكل صريح // عند تدمير t1, t2, t3، ينتظر main اكتمالها std::cout << "الخيط الرئيسي يواصل العمل"; return 0;}


من المهم أن نتذكر: std::jthread يدعم أيضًا آلية الإيقاف عبر std::stop_token، مما يسمح بإنهاء المهام الخلفية الطويلة بأمان.



2. مزامنة الوصول: كائنات المزامنة، الأقفال، والمتغيرات الشرطية



المشكلة الرئيسية في تعدد الخيوط هي سباقات البيانات. لمنعها، تُستخدم كائنات المزامنة. على المستوى المتقدم، يُوصى باستخدام std::scoped_lock (C++17) لقفل عدة كائنات مزامنة دون خطر الجمود (deadlock).



مثال مع std::scoped_lock:



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

std::mutex mtx1, mtx2;int shared_data = 0;

void safe_increment(int id) { // scoped_lock يقفل كلا كائني المزامنة بالترتيب، متجنبًا deadlock std::scoped_lock lock(mtx1, mtx2); ++shared_data; std::cout << "الخيط " << id << " زاد القيمة إلى " << shared_data << "";}

int main() { std::vector<std::jthread> threads; for (int i = 0; i < 10; ++i) { threads.emplace_back(safe_increment, i); } return 0;}


للسيناريوهات الأكثر تعقيدًا للمزامنة (مثل المنتج-المستهلك)، تُستخدم المتغيرات الشرطية (std::condition_variable). تسمح للخيوط بانتظار حدوث حدث معين دون استهلاك الموارد في الانتظار النشط.



3. العمليات الذرية ونموذج الذاكرة في C++



العمليات الذرية (std::atomic) هي أساس البرمجة الخالية من الأقفال (lock-free programming). تضمن أن قراءة وكتابة المتغير ستتم كعملية واحدة غير قابلة للتجزئة. ومع ذلك، على المستوى المتقدم، من المهم فهم نموذج الذاكرة في C++ ومستويات الترتيب (memory ordering).



المستويات الأساسية لـ memory ordering:

  • memory_order_relaxed — الحد الأدنى من الضمانات، فقط الذرية
  • memory_order_acquire — يضمن أن جميع القراءات والكتابات اللاحقة سترى نتائج العمليات السابقة
  • memory_order_release — يضمن أن جميع العمليات السابقة ستكون مرئية بعد هذه الكتابة
  • memory_order_acq_rel — مزيج من acquire و release

المدونات

توصيات الكتب