التزامن وتعدد الخيوط في C++: المستوى المتقدم
\n\nتتطلب التطبيقات الحديثة أقصى أداء، وتعدد الخيوط في C++ هو أحد الأدوات الرئيسية لتحقيق ذلك. ومع ذلك، فإن الآليات الأساسية (std::thread، std::mutex) تؤدي غالبًا إلى الحظر، وسباقات البيانات، وانخفاض الكفاءة. في هذه المقالة، سنستعرض التقنيات المتقدمة للعمل مع التزامن في C++: من العمليات الذرية إلى الهياكل الخالية من الأقفال والمهام غير المتزامنة.
\n\n1. العمليات الذرية وترتيب الذاكرة
\n\nتعتبر أقفال المزامنة التقليدية آلية ثقيلة. بالنسبة للعدادات البسيطة أو العلامات، من الأفضل استخدام std::atomic. فهو يضمن ذرية القراءة/الكتابة بدون أقفال. ومع ذلك، على المستوى المتقدم، من المهم فهم ترتيب الذاكرة - نموذج الذاكرة في C++ الذي يحدد رؤية التغييرات بين الخيوط.
أنظمة ترتيب الذاكرة الرئيسية:
\n- \n
memory_order_relaxed- الحد الأدنى من الضمانات، فقط الذرية. \n memory_order_consume- نادر الاستخدام، يعتمد على البيانات. \n memory_order_acquire- لا يمكن إعادة ترتيب جميع القراءات/الكتابات اللاحقة قبل هذه العملية. \n memory_order_release- تصبح جميع الكتابات السابقة مرئية للخيوط الأخرى بعد هذه العملية. \n memory_order_acq_rel- مزيج من acquire و release. \n memory_order_seq_cst- الأكثر صرامة، تناسق تسلسلي عالمي (افتراضي). \n
مثال على الاستخدام:
\n#include <atomic>\n#include <thread>\n#include <cassert>\n\nstd::atomic<bool> ready{false};\nint data = 0;\n\nvoid producer() {\n data = 42; // كتابة البيانات\n ready.store(true, std::memory_order_release); // نشر العلامة\n}\n\nvoid consumer() {\n while (!ready.load(std::memory_order_acquire)) // انتظار العلامة\n ;\n assert(data == 42); // مضمون رؤية data == 42\n}\n\nint main() {\n std::thread t1(producer);\n std::thread t2(consumer);\n t1.join();\n t2.join();\n return 0;\n}\n\nهنا، يضمن release و acquire أن كتابة data ستحدث قبل تعيين العلامة، والقراءة بعدها. هذا أسرع من المزامنة.
2. الهياكل الخالية من الأقفال
\n\nتتيح الخوارزميات الخالية من الأقفال تجنب الحظر باستخدام العمليات الذرية و CAS (Compare-And-Swap). مثال كلاسيكي هو المكدس الخالي من الأقفال المعتمد على القائمة المرتبطة أحادية الاتجاه.
\n\nتنفيذ مكدس خالٍ من الأقفال:
\n#include <atomic>\n#include <memory>\n\ntemplate<typename T>\nclass LockFreeStack {\nprivate:\n struct Node {\n T data;\n Node* next;\n Node(const T& val) : data(val), next(nullptr) {}\n };\n std::atomic<Node*> head{nullptr};\n\npublic:\n void push(const T& val) {\n Node* new_node = new Node(val);\n new_node->next = head.load(std::memory_order_relaxed);\n while (!head.compare_exchange_weak(\n new_node->next, new_node,\n std::memory_order_release,\n std::memory_order_relaxed)) {\n // حلقة حتى تنجح CAS\n }\n }\n\n bool pop(T& result) {\n Node* old_head = head.load(std::memory_order_relaxed);\n while (old_head != nullptr &&\n !head.compare_exchange_weak(\n old_head, old_head->next,\n std::memory_order_acquire,\n std::memory_order_relaxed)) {\n // انتظار\n }\n if (old_head == nullptr) return false;\n result = old_head->data;\n // تنبيه: في الكود الحقيقي، تحتاج إلى جامع قمامة أو مؤشرات خطر\n delete old_head;\n return true;\n }\n};\n\nملاحظات مهمة:
\n- \n