
في اليوم الثالث ، أصبح إصدار جديد من SObjectizer متوفرًا : 5.6.0 . السمة الرئيسية هي رفض التوافق مع الفرع السابق المستقر 5.5 ، والذي ظل يتطور بثبات على مدار أربع سنوات ونصف.
ظلت المبادئ الأساسية لتشغيل SObjectizer-5 كما هي. الاتصالات والوكلاء والتعاون والمرسلين لا تزال معنا. لكن شيئا ما تغير بشكل خطير ، تم إلقاء شيء بشكل عام. لذلك ، فقط أخذ SO-5.6.0 وإعادة ترجمة التعليمات البرمجية الخاصة بك سوف تفشل. يجب إعادة كتابة شيء ما. قد يلزم إعادة تصميم شيء ما.
لماذا اعتنى بالتوافق لعدة سنوات ، ثم قررنا أن نكسر كل شيء؟ وما الذي كسر أكثر شمولاً؟
سأحاول التحدث عن هذا في هذه المقالة.
لماذا تحتاج لكسر شيء ما؟
الأمر بهذه البساطة.
استوعبت SObjectizer-5.5 أثناء تطويرها العديد من الأشياء المختلفة والمتنوعة التي لم تكن مخططة في الأصل ، ونتيجة لذلك ، فقد شكلت الكثير من العكازات والدعائم في الداخل. مع كل إصدار جديد ، أصبح إضافة شيء جديد إلى SO-5.5 أصعب وأصعب. وأخيرا ، على السؤال "لماذا نحتاج كل هذا؟" لم يتم العثور على إجابة مناسبة.
وبالتالي فإن السبب الأول هو إعادة تعقيد حوصرات SObjectizer.
السبب الثاني هو أننا تعبنا بغباء من التركيز على المجمعين C ++ القديمة. بدأ فرع 5.5 في عام 2014 ، عندما كان لدينا ، إذا لم أكن مخطئًا ، gcc-4.8 و MSVS2013. وعلى هذا المستوى ، ما زلنا نواصل الحفاظ على حاجز متطلبات مستوى الدعم لمعيار C ++.
في البداية ، كان لدينا "مصلحة أنانية" في هذا. بالإضافة إلى ذلك ، نظرنا لبعض الوقت إلى المتطلبات المنخفضة لجودة الدعم لمعيار C ++ باعتبارها "ميزتنا التنافسية".
ولكن الوقت يمضي ، "المصلحة الأنانية" قد انتهت. بعض الفوائد من هذه "الميزة التنافسية" غير مرئية. ربما سيكونون كذلك ، إذا عملنا مع C ++ 98 على الإطلاق ، فسنكون مهتمين بالمشروع الدموي. لكن المشروع الدموي في مثل هؤلاء ، من حيث المبدأ ، ليست مهتمة. لذلك ، فقد تقرر التوقف عن الحد من أنفسنا واتخاذ شيء أعذب. لذلك أخذنا المستقر الجديد في الوقت الحالي: C ++ 17.
من الواضح ، لن يحب الجميع هذا الحل ، بعد كل شيء ، بالنسبة للعديد من C ++ 17 ، هذه الآن ميزة رائدة لا يمكن الوصول إليها ، والتي لا تزال بعيدة جدًا.
ومع ذلك ، قررنا مثل هذا الخطر. ومع ذلك ، فإن عملية الترويج لـ SObjectizer لا تسير بسرعة ، لذلك عندما يصبح SObjectizer أكثر أو أقل انتشارًا في الطلب ، لن يكون C ++ 17 "حافة رائدة". بدلاً من ذلك ، سيتم التعامل معها كما هي الآن في C ++ 11.
بشكل عام ، بدلاً من الاستمرار في إنشاء عكازات باستخدام مجموعة فرعية من C ++ 11 ، قررنا إعادة تشكيل الأجزاء الداخلية من SObjectizer بشكل جدي باستخدام C ++ 17. لبناء قاعدة يمكن أن يتطور عليها SObjectizer تدريجياً خلال السنوات الأربع أو الخمس القادمة.
ما الذي تغير بشكل خطير في SObjectizer-5.6؟
الآن ، دعنا ننتقل لفترة وجيزة إلى بعض التغييرات الأكثر لفتا للنظر.
لم يعد التعاون بين الوكلاء أسماء سلسلة
المشكلة
منذ البداية ، طالب SObjectizer-5 بأن يكون لكل تعاون اسم سلسلة فريد خاص به. تم توريث هذه الميزة من SObjectizer الخامس من SObjectizer السابق ، الرابع.
وفقا لذلك ، SObjectizer اللازمة لتخزين أسماء التعاون المسجلة. تحقق تفردهم في التسجيل. ابحث عن التعاون بالاسم أثناء إلغاء التسجيل ، إلخ ، إلخ.
منذ الإصدارات الأولى ، تم استخدام مخطط بسيط في SObjectzer-5: قاموس واحد للتعاون المسجلة المحمي بواسطة mutex. عند تسجيل التعاون ، يتم التقاط كائن المزامنة (mutex) ، وتفرد اسم التعاون ، ووجود أحد الوالدين ، إلخ. بعد التدقيق ، يتم تعديل القاموس ، وبعد ذلك يتم تحرير كائن المزامنة. هذا يعني أنه في نفس الوقت الذي يبدأ فيه تسجيل / إلغاء تسجيل العديد من التعاونيات في وقت واحد ، ثم في بعض النقاط سوف تتوقف وتنتظر حتى تكتمل إحدى العمليات مع القاموس التعاوني. وبسبب هذا ، لم تكن العمليات التعاونية جيدة.
هذا ما أردت أن أتخلص منه من أجل تحسين الوضع بسرعة تسجيل التعاون.
قرار
تم النظر في طريقتين رئيسيتين لحل هذه المشكلة.
أولاً ، تخزين أسماء السلسلة ، ولكن تغيير طريقة تخزين القاموس بحيث يمكن توسيع نطاق عملية تسجيل التعاون. على سبيل المثال ، مشاركة القاموس ، أي تقسيمها إلى عدة قطع ، كل منها سيكون محميًا بواسطة كائن المزامنة الخاص به.
ثانياً ، رفض كامل لأسماء السلسلة واستخدام بعض المعرفات المعينة بواسطة SObjectizer.
نتيجة لذلك ، اخترنا الطريقة الثانية وتخلينا تمامًا عن تسمية التعاونيات. الآن في SObjectizer ، يوجد شيء مثل coop_handle
، أي مؤشر يتم إخفاء محتوياته عن المستخدم ، ولكن يمكن مقارنته مع std::weak_ptr
.
إرجاع coop_handle
عند تسجيل التعاون:
auto coop = env.make_coop(); ...
يجب استخدام هذا المقبض لإلغاء تسجيل التعاون:
auto coop = env.make_coop(); ...
أيضًا ، يجب استخدام هذا المقبض عند إنشاء علاقة بين الوالدين والطفل:
كما تم تغيير هيكل مستودع التعاون داخل بيئة SObjectizer بشكل كبير. إذا كان قبل الإصدار 5.5 ضمنيًا قاموسًا مشتركًا واحدًا ، فكل تعاون الآن هو مستودع للروابط الخاصة بالتعاونيات الفرعية. أي تشكل التعاونيات شجرة ذات جذر في تعاونية جذر خاصة مخفية عن المستخدم.
مثل هذا الهيكل يجعل من الممكن توسيع نطاق عمليات register_coop
و deregister_coop
بشكل أفضل: لا يحدث الحظر المتبادل للعمليات الموازية إلا إذا كان كلاهما ينتمي إلى نفس التعاون الأبوي. للتوضيح ، إليك نتيجة لإطلاق معيار خاص يقيس أداء العمليات بالتعاون مع جهاز الكمبيوتر المحمول القديم الخاص بي مع Ubuntu 16.04 و GCC-7.3:
_test.bench.so_5.parallel_parent_child -r 4 -l 7 -s 5 Configuration: roots: 4, levels: 7, level-size: 5 parallel_parent_child: 15.69s 488280 488280 488280 488280 Total: 1953120
أي الإصدار 5.6.0 تعاملت مع ما يقرب من 2M التعاون في ~ 15.5 ثانية.
وإليكم الإصدار 5.5.24.4 ، آخر فرع 5.5 في الوقت الحالي:
_test.bench.so_5.parallel_parent_child -r 4 -l 7 -s 5 Configuration: roots: 4, levels: 7, level-size: 5 parallel_parent_child: 46.856s 488280 488280 488280 488280 Total: 1953120
نفس السيناريو ، ولكن النتيجة أسوأ ثلاث مرات.
هناك نوع واحد فقط من المرسلين اليسار
المرسلون أحد أحجار الزاوية في SObjectizer. إن المرسلين هم الذين يحددون مكان وكيفية قيام الوكلاء بمعالجة رسائلهم. لذلك ، دون فكرة المرسلين ، ربما لن يكون هناك SObjectizer.
ومع ذلك ، فإن المرسلين أنفسهم قد تطوروا وتطوروا وتطوروا مسبقًا إلى درجة أنه لم يكن من الصعب علينا أن نرسل مرسلًا جديدًا لـ SObjectizer-5.5. لكن مزعجة للغاية. ومع ذلك ، دعنا نأخذها بالترتيب.
في البداية ، يمكن إنشاء جميع المرسلين الذين يحتاجهم التطبيق فقط في بداية SObjectizer:
so_5::launch( []( so_5::environment_t & env ) { },
لم أقم بإنشاء المدير الضروري قبل البداية - كل شيء ، إنه خطأي ، لا يمكنك تغيير أي شيء.
من الواضح أن هذا غير مريح ومع توسيع سيناريوهات الاستخدام لـ SObjectizer ، كان من الضروري حل هذه المشكلة. لذلك ، add_dispatcher_if_not_exists
طريقة add_dispatcher_if_not_exists
، والتي تحقق من وجود المرسل ، وإذا لم يكن هناك أي شيء ، يُسمح بإنشاء مثيل جديد:
so_5::launch( []( so_5::environment_t & env ) { ...
وكانت هذه المرسلات تسمى العامة. كان للمرسلين العامين أسماء فريدة. وباستخدام هذه الأسماء ، تم ربط العملاء بالمرسلين:
so_5::launch( []( so_5::environment_t & env ) { ...
لكن المرسلين العامين لديهم ميزة واحدة غير سارة. بدأوا العمل فور إضافتهم إلى SObjectizer Environment واستمروا في العمل حتى أكملت SObjectizer Environment عملها.
مرة أخرى ، مع مرور الوقت ، بدأت تتدخل. كان من الضروري التأكد من إمكانية إضافة المرسلين حسب الحاجة وأن المرسلين الذين أصبحوا غير ضروريين قد تم حذفهم تلقائيًا.
لذلك كان هناك المرسلون "الخاصون". هؤلاء المرسلون لم يكن لديهم أسماء وعاشوا طالما كانت هناك إشارات إليهم. يمكن إنشاء المرسلين الخاصين في أي وقت بعد بدء تشغيل بيئة SObjectizer ، فقد تم تدميرهم تلقائيًا.
بشكل عام ، تبين أن المرسلين الخاصين حلقة ناجحة جدًا في تطور المرسلين ، لكن العمل معهم كان مختلفًا جدًا عن العمل مع المرسلين العامين:
so_5::launch( []( so_5::environment_t & env ) { ...
أكثر المرسلين القطاعين العام والخاص اختلفت في التنفيذ. لذلك ، من أجل عدم تكرار الشفرة والكتابة بشكل منفصل المرسل العام والخاص بشكل منفصل من نفس النوع ، اضطررت إلى استخدام إنشاءات معقدة إلى حد ما مع القوالب والميراث.
ونتيجة لذلك ، تعبت من مرافقة كل هذا التنوع ، وفي SObjectizer-5.6 لم يتبق سوى نوع واحد من المرسلين. في الواقع ، هذا هو التناظرية من المرسلين خاصة. ولكن فقط دون ذكر صريح لكلمة "خاص". حتى الآن سيتم كتابة الجزء الموضح أعلاه على النحو التالي:
so_5::launch( []( so_5::environment_t & env ) { ...
لا يوجد سوى وظائف مجانية للإرسال ، send_delayed و send_periodic left
يعد تطوير واجهة برمجة التطبيقات لإرسال الرسائل إلى SObjectizer هو المثال الأكثر وضوحًا على كيفية تغيير SObjectizer مع تحسن دعم C ++ 11 في المترجمات المتاحة لنا.
أولاً ، تم إرسال رسائل مثل هذا:
mbox->deliver_message(new my_message(...));
أو ، إذا اتبعت "توصيات أفضل مربي الكلاب" (ج):
std::unique_ptr<my_message> msg(new my_message(...)); mbox->deliver_message(std::move(msg));
ومع ذلك ، فقد وصلنا إلى المجمعين التابعين لنا مع دعم لقوالب varadic وظهرت وظائف الإرسال. أصبح من الممكن أن تكتب مثل هذا:
send<my_message>(target, ...);
صحيح ، لقد استغرق الأمر وقتًا أطول لعائلة بأكملها send_to_agent
من send
بسيط ، بما في ذلك send_to_agent
، send_delayed_to_agent
، إلخ. وبعد ذلك ، لجعل هذه العائلة أضيق نطاقًا من المجموعة المألوفة send
، send_delayed
send_periodic
.
ولكن على الرغم من حقيقة أن مجموعة وظائف الإرسال قد تم تشكيلها منذ فترة طويلة وكانت الطريقة الموصى بها لإرسال الرسائل لعدة سنوات ، فإن الأساليب القديمة مثل deliver_message
و single_timer
و single_timer
كانت متاحة للمستخدم.
ولكن في الإصدار 5.6.0 ، لم يتم حفظ send_periodic
وظائف send
المجاني و send_delayed
و send_periodic
في واجهة برمجة تطبيقات SObjectizer العامة. تم حذف كل شيء آخر تمامًا أو نقله إلى مساحات أسماء SObjectizer الداخلية.
لذا في SObjectizer-5.6 ، أصبحت واجهة إرسال الرسائل أخيرًا كما لو كانت لدينا برامج ترجمة مع دعم C ++ 11 عادي من البداية. حسنًا ، بالإضافة إلى ذلك ، إذا كان لدينا خبرة في استخدام C ++ 11 العادي جدًا.
مع send_delayed
و send_periodic
في الإصدارات السابقة من SObjectizer ، كان هناك حادث آخر.
لاستخدام المؤقت ، يجب أن يكون لديك حق الوصول إلى SObjectizer Environment. داخل الوكيل يوجد رابط إلى بيئة SObjectizer. وداخل mchain هناك مثل هذا الرابط. ولكن داخل mbox لم تكن هناك. لذلك ، إذا تم إرسال رسالة معلقة إلى وكيل أو إلى mchain ، فإن المكالمة send_delayed
كالتالي:
send_delayed<my_message>(target_agent, pause, ...); send_delayed<my_message>(target_mchain, pause, ...);
بالنسبة لحالة mbox ، كان علينا أن نأخذ رابطًا إلى SObjectizer Environment من مكان آخر:
send_delayed<my_message>(this->so_environment(), target_mbox, pause, ...);
هذه الميزة من send_delayed
و send_periodic
كانت منشقة بسيطة. وهو ليس الكثير من التدخل ، ولكن مزعج جدا. وكل ذلك لأننا في البداية لم نبدأ في تخزين الرابط إلى SObjectizer Environment في mbox-ahs.
كان انتهاك التوافق مع الإصدارات السابقة سببًا جيدًا للتخلص من هذا الشظية.
يمكنك الآن معرفة بيئة SObjectizer من mbox التي تم إنشاؤها من أجلها. وهذا جعل من الممكن استخدام send_periodic
و send_periodic
لأي نوع من مستلم رسالة المؤقت:
send_delayed<my_message>(target_agent, pause, ...); send_delayed<my_message>(target_mchain, pause, ...); send_delayed<my_message>(target_mbox, pause, ...);
بالمعنى الحرفي ، "تافه ، ولكن لطيفة".
لا مزيد من الوكلاء المخصصين
كما يقول المثل ، "كل حادث له اسم أول واسم الأوسط واسم العائلة". في حالة الوكلاء المخصصين ، هذا هو الاسم الأول والاسم الأوسط والاسم الأخير :(
النقطة هي هذا. عندما بدأنا نتحدث عن SObjectizer-5 في الأماكن العامة ، سمعنا الكثير من اللوم على لفظ الشفرة لأمثلة SObjectizer. وشخصيا ، بدا لي هذا الفعل اللفظي مشكلة خطيرة أحتاج إلى التعامل معها بجدية.
أحد مصادر الفعل هو الحاجة إلى أن يرث الوكلاء من agent_t
النوع الأساسي الخاص. ومن هذا ، يبدو أنه لا يوجد مفر. أم لا؟
لذلك كان هناك وكلاء مخصصون ، أي الوكلاء ، من أجل تحديد أنه لم يكن من الضروري كتابة فصل منفصل ، كان يكفي فقط تعيين رد الفعل على الرسائل في شكل وظائف lambda. على سبيل المثال ، يمكن كتابة مثال ping-pong الكلاسيكي على العوامل المخصصة مثل هذا:
auto pinger = coop->define_agent(); auto ponger = coop->define_agent(); pinger .on_start( [ponger]{ so_5::send< msg_ping >( ponger ); } ) .event< msg_pong >( pinger, [ponger]{ so_5::send< msg_ping >( ponger ); } ); ponger .event< msg_ping >( ponger, [pinger]{ so_5::send< msg_pong >( pinger ); } );
أي لا الطبقات الخاصة بهم. نحن فقط نطلق على define_agent()
التعاون ونحصل على نوع من كائن الوكيل ، والذي يمكنك الاشتراك في الرسائل الواردة.
لذلك في SObjectizer-5 كان هناك فصل إلى وكلاء العادية والمخصصة.
التي لم تجلب أي مكافآت واضحة ، إلا أن تكاليف العمالة الإضافية لمرافقة مثل هذا الفصل. ومع مرور الوقت ، أصبح من الواضح أن العملاء المخصصين يشبهون حقيبة بدون مقبض: من الصعب حملها ومن المؤسف أن تتركها. ولكن أثناء العمل على SObjectizer-5.6 ، فقد تقرر إنهاء الخدمة.
في الوقت نفسه ، تم تعلم درس آخر ، وربما أكثر أهمية: في أي مناقشة عامة للأداة على الإنترنت ، سيشارك عدد كبير من الناس غير مبالين بماهية الأداة ، ولماذا هناك حاجة إليها ، ولماذا من المفترض أن تستخدم ، وما إلى ذلك. من المهم بالنسبة لهم ببساطة التعبير عن رأيهم القوي. في قسم اللغة الروسية من الإنترنت ، بالإضافة إلى ذلك ، لا يزال من المهم للغاية أن ننقل لمطوري الأداة كيف أنهم أغبياء وغير متعلمين ، ومدى عدم الحاجة إلى نتيجة عملهم.
لذلك ، يجب أن تكون حذرا للغاية في ما قيل لك. ويمكنك الاستماع (ثم بعناية) فقط إلى ما يقال هنا في هذا السياق: "لقد حاولت أن أفعل هذا على الآلة الخاصة بك ولا أحب مقدار الكود الذي حصلت عليه هنا." حتى هذه الرغبات يجب أن تعامل بعناية فائقة: "سأأخذ تطويرك إذا كان الأمر أسهل هنا وهنا."
لسوء الحظ ، فإن مهارة "التصفية" التي قالها "المهنئين" على الإنترنت قبل حوالي خمس سنوات كانت أقل بكثير من الآن. وبالتالي ، مثل هذه التجربة المحددة كوكلاء مخصصين في SObjectizer.
لم يعد SObjectizer-5.6 يدعم تفاعل العامل المتزامن
موضوع التفاعل المتزامن بين الوكلاء قديم جدًا ومؤلِّم.
بدأت في أيام SObjectizer-4. وفي SObjectizer-5 تابع. حتى الآن ، وأخيرا ، ما يسمى طلبات الخدمة . التي في البداية ، باعتراف الجميع ، كانت مخيفة مثل الموت. ولكن بعد ذلك تمكنت من منحهم نظرة أكثر أو أقل لائق .
ولكن تبين أن هذا هو الحال عندما خرج أول فطيرة متكتلة :(
داخل SObjectizer ، اضطررت إلى تنفيذ تسليم الرسائل العادية ومعالجتها بطريقة واحدة ، وتسليم الطلبات المتزامنة ومعالجتها بطريقة أخرى. من المحزن بشكل خاص أن هذه الميزات يجب أن تؤخذ في الاعتبار ، بما في ذلك عند تطبيق mbox-s الخاصة بك.
وبعد إضافة وظائف رسائل المغلف إلى SObjectizer ، أصبح من الضروري النظر بشكل أكثر تكرارا وبصورة أكثر شمولية في الاختلافات بين الرسائل العادية والطلبات المتزامنة.
بشكل عام ، مع وجود طلبات متزامنة أثناء صيانة / تطوير SObjectizer ، كان هناك الكثير من الصداع. لدرجة أنه في البداية كانت هناك رغبة ملموسة للتخلص من هذه الطلبات المتزامنة للغاية . ثم تحقق هذه الرغبة.
وهكذا في SObjectizer-5.6 ، يمكن للوكلاء التفاعل مرة أخرى فقط من خلال الرسائل غير المتزامنة.
ونظرًا لأنه في بعض الأحيان لا تزال هناك حاجة إلى تفاعل متزامن ، تم تقديم دعم لهذا النوع من التفاعل إلى مشروع so5extra المصاحب :
أي يختلف العمل الآن مع الطلبات المتزامنة اختلافًا أساسيًا في أن معالج الطلب لا يُرجع قيمة من أسلوب المعالج ، كما كان من قبل. بدلاً من ذلك ، يتم make_reply
الأسلوب make_reply
.
يعد التطبيق الجديد جيدًا حيث يتم إرسال كل من الطلب والاستجابة داخل SObjectizer مثل الرسائل غير المتزامنة العادية. في الواقع ، يعد make_reply
أكثر تحديدًا send
.
والأهم من ذلك أن التطبيق الجديد سمح لنا بالحصول على وظائف لم يكن من الممكن الوصول إليها من قبل:
- يمكن الآن حفظ و / أو إعادة توجيه الطلبات المتزامنة (مثل
request_reply_t<Request, Reply>
كائنات request_reply_t<Request, Reply>
) إلى معالجات أخرى. ما الذي يجعل من الممكن تنفيذ مخططات موازنة التحميل المختلفة ؛ - يمكنك جعل الرد على الطلب يأتي في mbox منتظم للوكيل الذي يبدأ الطلب. وسيعالج الوكيل الأولي الاستجابة بالطريقة المعتادة ، مثل أي رسالة أخرى ؛
- يمكنك إرسال عدة طلبات إلى مستلمين مختلفين في آن واحد ، ثم تحليل الردود الواردة منهم بالترتيب الذي تم استلامه:
using first_dialog = so_5::extra::sync::request_reply_t<first_request, first_reply>; using second_dialog = so_5::extra::sync::request_reply_t<second_request, second_reply>;
لذلك ، يمكننا القول أنه مع التفاعل المتزامن في SObjectizer ، حدث ما يلي:
- لوقت طويل ذُبح لأسباب أيديولوجية ؛
- ثم تمت إضافته واتضح أنه في بعض الأحيان يكون هذا التفاعل مفيدًا ؛
- لكن التجربة أظهرت أن التنفيذ الأول ليس ناجحًا جدًا ؛
- تم إلقاء التنفيذ القديم بالكامل ، واقترح تنفيذ جديد في المقابل.
لقد عملوا على أخطائهم ، بشكل عام.
استنتاج
تحدثت هذه المقالة ، باختصار شديد ، عن العديد من التغييرات في SObjectizer-5.6.0 والأسباب الكامنة وراء هذه التغييرات.
يمكن العثور على قائمة أكثر اكتمالا من التغييرات هنا .
في الختام ، أريد أن أقدم أولئك الذين لم يجربوا برنامج SObjectizer حتى الآن ، خذوه وجربه. وشاركنا مشاعرك: ما أعجبك وما لم يعجبك وما كان مفقودًا.
نستمع بعناية لجميع التعليقات / الاقتراحات البناءة. علاوة على ذلك ، في السنوات الأخيرة ، يتم تضمين ما يحتاجه شخص ما فقط في SObjectizer. لذلك إذا لم تخبرنا بما تريده في SObjectizer ، فلن يظهر هذا. وإذا قلت لي ، فمن يعرف ... ؛)
المشروع الآن يعيش ويتطور هنا . بالنسبة لأولئك الذين اعتادوا على استخدام GitHub فقط ، هناك مرآة GitHub . هذه المرآة جديدة تمامًا ، لذا يمكنك تجاهل قلة النجوم.
PS. يمكنك متابعة أخبار SObjectizer ذات الصلة في مجموعة Google هذه . هناك يمكنك إثارة القضايا المتعلقة SObjectizer.