منذ بعض الوقت ، انتشر رابط لمقال "فلاسفة الطعام الحديث" على موارد مثل رديت وهاكر نيوز. المقال مثير للاهتمام ، فهو يعرض العديد من الحلول لهذه المهمة المعروفة ، والتي تم تنفيذها في لغة C ++ الحديثة باستخدام نهج قائم على المهام. إذا لم يقرأ أحد هذه المقالة بعد ، فمن المنطقي قضاء الوقت وقراءته.
ومع ذلك ، لا أستطيع أن أقول إن الحلول المقدمة في المقالة تبدو بسيطة ومفهومة بالنسبة لي. هذا ربما يرجع إلى استخدام المهام. يتم إنشاء الكثير منهم وإرسالهم من خلال مجموعة متنوعة من المرسلين / المتسللين. لذلك ليس من الواضح دائمًا مكان وتوقيت وما هي المهام التي يتم تنفيذها.
علاوة على ذلك ، فإن النهج القائم على المهام ليس هو النهج الوحيد الممكن لحل هذه المشاكل. لماذا لا ترى كيف يتم حل مهمة "فلاسفة الطعام" من خلال نماذج من الممثلين و CSP؟
لذلك ، حاولت البحث وتنفيذ العديد من الحلول لهذه المشكلة باستخدام كلاً من الممثلين و CSP. يمكن العثور على رمز هذه الحلول في المستودع على GitHub . وتحت القاطع ، تفسيرات وتفسيرات ، لذلك أي شخص مهتم ، مرحب به تحت القص.
بعض الكلمات الشائعة
لم يكن لدي هدف يتمثل في تكرار القرارات الواردة في مقالة "فلاسفة الطعام المعاصرين" تمامًا ، لا سيما وأنني لا أحب شيئًا مهمًا واحدًا: في الواقع ، الفيلسوف لا يفعل شيئًا في تلك القرارات. يقول فقط "أريد أن آكل" ، ومن ثم يعطيه أحدهم شوكًا بطريقة سحرية ، أو يقول "الآن لن ينجح".
من الواضح لماذا لجأ المؤلف إلى هذا السلوك: فهو يسمح باستخدام نفس تطبيق "الفيلسوف" بالاقتران مع تطبيقات مختلفة لـ "البروتوكولات". ومع ذلك ، يبدو لي شخصياً أن الأمر أكثر إثارة للاهتمام عندما يحاول "الفيلسوف" أخذ أحد المكونات أولاً ثم الآخر. وعندما يجبر "الفيلسوف" على التعامل مع المحاولات الفاشلة للقبض على الشوك.
هذه بالضبط هي إدراك مهمة "فلاسفة الطعام" التي حاولت القيام بها. في الوقت نفسه ، استخدمت بعض الحلول نفس الأساليب كما في المقالة المذكورة (على سبيل المثال ، تم تنفيذها بواسطة بروتوكولي ForkLevelPhilosopherProtocol و WaiterFair).
قمت ببناء قراري على أساس SObjectizer ، والذي من غير المرجح أن يفاجئ أولئك الذين قرأوا مقالاتي من قبل. إذا لم يسمع أحد عن SObjectizer حتى الآن ، باختصار: هذا هو واحد من "أطر الفاعل" المفتوحة والمطوّرة المفتوحة المصدر لـ C ++ ( CAF و QP / C ++ يمكن ذكرها من بين أمور أخرى). آمل أن تكون الأمثلة المذكورة أعلاه مع تعليقاتي واضحة بما يكفي حتى بالنسبة لأولئك الذين ليسوا على دراية بـ SObjectizer. إذا لم يكن كذلك ، سأكون سعيدًا بالإجابة على الأسئلة الواردة في التعليقات.
حلول الفاعلين
سنبدأ مناقشة الحلول المنفذة مع الحلول القائمة على الممثلين. أولاً ، فكر في تطبيق حل Edsger Dijkstra ، ثم انتقل إلى العديد من الحلول الأخرى وشاهد كيف يختلف سلوك كل حل.
قرار ديكسترا
Edsger Dijkstra ، لم يكتف بصياغة مهمة "تناول الطعام فيلهوفوس" (صياغته باستخدام "الشوك" و "السباغيتي" التي عبر عنها توني هوار) ، بل اقترح أيضًا حلًا بسيطًا وجميلًا للغاية. وهي: يجب على الفلاسفة الاستيلاء على الشوك فقط من أجل زيادة عدد الشوك ، وإذا تمكن الفيلسوف من أخذ الشوكة الأولى ، فلن يتركها حتى يتلقى الشوكة الثانية.
على سبيل المثال ، إذا كان الفيلسوف يحتاج إلى استخدام شوكات برقمين 5 و 6 ، فيجب على الفيلسوف أولاً أن يأخذ شوكة برقم 5. عندها فقط يمكن أن يأخذ شوكة برقم 6. وهكذا ، إذا كانت الشوكات ذات الأرقام الأدنى على يسار الفلاسفة ، فيجب على الفيلسوف أولاً ، استخدم الشوكة اليسرى وعندها فقط يمكنه أخذ الشوكة اليمنى.
يقوم الفيلسوف الأخير في القائمة ، الذي يتعين عليه التعامل مع الشوكات بالأرقام (N-1) و 0 ، بالعكس: يأخذ أولاً الشوكة اليمنى برقم 0 ، ثم الشوكة اليسرى بالرقم (N-1).
لتنفيذ هذا النهج ، ستكون هناك حاجة إلى نوعين من العناصر الفاعلة: واحد للشوك والآخر للفلاسفة. إذا أراد الفيلسوف أن يأكل ، يرسل رسالة إلى ممثل الشوكة المقابل لالتقاط الشوكة ، ويستجيب ممثل الشوكة برسالة استجابة.
رمز لتنفيذ هذا النهج يمكن أن ينظر إليه هنا .
الرسائل
قبل التحدث عن الجهات الفاعلة ، تحتاج إلى إلقاء نظرة على الرسائل التي سيتبادلها الممثلون:
struct take_t { const so_5::mbox_t m_who; std::size_t m_philosopher_index; }; struct taken_t : public so_5::signal_t {}; struct put_t : public so_5::signal_t {};
عندما يريد الممثل الفيلسوف أخذ القابس ، يرسل رسالة take_t
إلى take_t
الشوكة ، ويستجيب ممثل الشوكة برسالة taken_t
. عندما ينتهي الممثل الفيلسوف من الأكل ويريد إعادة الشوك إلى الطاولة ، يرسل رسائل put_t إلى put_t
فوركس.
في رسالة take_t
، يشير الحقل take_t
إلى صندوق البريد (المعروف أيضًا باسم mbox) لممثل الفيلسوف. يجب إرسال رسالة استجابة taken_t
إلى taken_t
هذا. لا يتم استخدام الحقل الثاني من take_t
في هذا المثال ، فسنحتاجه عندما نصل إلى تطبيقات waiter_with_queue و waiter_with_timestamps.
الممثل شوكة
الآن يمكننا أن ننظر إلى ما هو ممثل شوكة. هنا هو رمزه:
class fork_t final : public so_5::agent_t { public : fork_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } {} void so_define_agent() override {
يجب أن يستمد كل ممثل في SObjectizer من agent_t
class. ما نراه هنا لنوع fork_t
.
تم تجاوز الأسلوب so_define_agent()
في فئة so_define_agent()
. هذه طريقة خاصة ، يتم استدعاؤها تلقائيًا بواسطة SObjectizer عند تسجيل وكيل جديد. في طريقة so_define_agent()
، يتم "تكوين" so_define_agent()
للعمل في SObjectizer: تتغير حالة البدء ، ويتم الاشتراك في الرسائل الضرورية.
كل ممثل في SObjectizer هو جهاز حالة له حالات (حتى لو كان الممثل يستخدم حالة افتراضية واحدة فقط). يحتوي الممثل fork_t
على حالتين: مجاني ويتم fork_t
. عندما يكون الممثل في حالة حرة ، يمكن للفيلسوف "التقاط" القابس. وبعد التقاط "الشوكة" ، يجب أن يذهب ممثل fork_t
إلى الحالة التي تم التقاطها . داخل فئة fork_t
يتم تمثيل الحالات st_free
و st_taken
النوع state_t
الخاص.
تسمح لك الدول بمعالجة الرسائل الواردة بطرق مختلفة. على سبيل المثال ، في الحالة الحرة ، يستجيب الوكيل فقط لـ take_t
ويكون رد الفعل هذا بسيطًا للغاية: تتغير حالة الممثل taken_t
الاستجابة taken_t
:
st_free .event( [this]( mhood_t<take_t> cmd ) { this >>= st_taken; so_5::send< taken_t >( cmd->m_who ); } );
بينما يتم تجاهل كل الرسائل الأخرى ، بما في ذلك put_t
في الحالة الحرة ، بكل بساطة.
في الحالة التي يتم التقاطها ، يقوم الفاعل بمعالجة رسالتين ، وحتى في رسالة take_t
فإنه يعالج بشكل مختلف:
st_taken .event( [this]( mhood_t<take_t> cmd ) { m_queue.push( cmd->m_who ); } ) .event( [this]( mhood_t<put_t> ) { if( m_queue.empty() ) this >>= st_free; else { const auto who = m_queue.front(); m_queue.pop(); so_5::send< taken_t >( who ); } } );
يعتبر معالج put_t
أكثر إثارة للاهتمام put_t
: إذا كانت قائمة انتظار الفلاسفة الفارغة ، فيمكننا العودة مجانًا ، ولكن إذا لم تكن فارغة ، فيجب إرسال taken_t
.
الفيلسوف الممثل
رمز الممثل الفيلسوف أكثر ضخامة ، لذلك لن أعطيها هنا بالكامل. سنناقش فقط الشظايا الأكثر أهمية.
لدى الفيلسوف الممثل حالات أكثر قليلاً:
state_t st_thinking{ this, "thinking.normal" }; state_t st_wait_left{ this, "wait_left" }; state_t st_wait_right{ this, "wait_right" }; state_t st_eating{ this, "eating" }; state_t st_done{ this, "done" };
يبدأ الممثل عمله في حالة تفكير ، ثم ينتقل إلى wait_left ، ثم إلى wait_right ، ثم إلى الأكل . من الأكل ، يمكن للممثل العودة إلى التفكير أو يمكن القيام به إذا كان الفيلسوف قد أكل كل ما يجب أن يكون.
يمكن تمثيل مخطط الدولة للفيلسوف الممثل على النحو التالي:

يتم وصف منطق سلوك الممثل في تنفيذ طريقة so_define_agent()
:
void so_define_agent() override {
ولعل النقطة الوحيدة التي ينبغي التأكيد عليها بشكل خاص هي النهج المتبع في تقليد عمليات "التفكير" و "الأكل". لا يوجد this_thread::sleep_for
في رمز الممثل أو أي طريقة أخرى لمنع سلسلة العمل الحالية. بدلاً من ذلك ، يتم استخدام الرسائل المعلقة. على سبيل المثال ، عندما يدخل ممثل في حالة الأكل ، يرسل إلى نفسه رسالة stop_eating_t
معلقة. يتم تقديم هذه الرسالة إلى مؤقت SObjectizer ويقوم الموقت بتسليم الرسالة إلى الممثل عندما يحين الوقت.
يتيح لك استخدام الرسائل المؤجلة تشغيل جميع العناصر الفاعلة في سياق سلسلة عمليات واحدة. تحدث تقريبًا ، مؤشر ترابط واحد يقرأ رسائل من قائمة انتظار ويخرج معالج الرسالة التالية من ممثل المستلم المطابق. سيتم مناقشة المزيد حول سياقات العمل للجهات الفاعلة أدناه.
النتائج
قد تبدو نتائج هذا التطبيق كما يلي (جزء صغير):
Socrates: tttttttttttLRRRRRRRRRRRRRREEEEEEEttttttttLRRRRRRRRRRRRRREEEEEEEEEEEEE Plato: ttttttttttEEEEEEEEEEEEEEEEttttttttttRRRRRREEEEEEEEEEEEEEttttttttttLLL Aristotle: ttttEEEEEtttttttttttLLLLLLRRRREEEEEEEEEEEEttttttttttttLLEEEEEEEEEEEEE Descartes: tttttLLLLRRRRRRRREEEEEEEEEEEEEtttLLLLLLLLLRRRRREEEEEEttttttttttLLLLLL Spinoza: ttttEEEEEEEEEEEEEttttttttttLLLRRRREEEEEEEEEEEEEttttttttttRRRREEEEEEtt Kant: ttttttttttLLLLLLLRREEEEEEEEEEEEEEEttttttttttLLLEEEEEEEEEEEEEEtttttttt Schopenhauer: ttttttEEEEEEEEEEEEEttttttLLLLLLLLLEEEEEEEEEttttttttLLLLLLLLLLRRRRRRRR Nietzsche: tttttttttLLLLLLLLLLEEEEEEEEEEEEEttttttttLLLEEEEEEEEEttttttttRRRRRRRRE Wittgenstein: ttttEEEEEEEEEEtttttLLLLLLLLLLLLLEEEEEEEEEttttttttttttRRRREEEEEEEEEEEt Heidegger: tttttttttttLLLEEEEEEEEEEEEEEtttttttLLLLLLREEEEEEEEEEEEEEEtttLLLLLLLLR Sartre: tttEEEEEEEEEttttLLLLLLLLLLLLRRRRREEEEEEEEEtttttttLLLLLLLLRRRRRRRRRRRR
اقرأ هذا على النحو التالي:
t
يدل على أن الفيلسوف هو "التفكير" ؛L
يعني أن الفيلسوف يتوقع التقاط الشوكة اليسرى (في حالة wait_left ) ؛R
تعني أن الفيلسوف يتوقع التقاط الشوكة الصحيحة (في حالة wait_right ) ؛E
يعني أن الفيلسوف "يأكل".
يمكننا أن نرى أن سقراط لا يمكن أن يأخذ الشوكة على اليسار إلا بعد أن يعطيه سارتر. بعد ذلك سوف ينتظر سقراط حتى يطلق أفلاطون الشوكة المناسبة. فقط بعد هذا سقراط سيكون قادرا على تناول الطعام.
قرار بسيط بدون محكم (النادل)
إذا قمنا بتحليل نتيجة قرار ديكسترا ، سنرى أن الفلاسفة يقضون الكثير من الوقت في انتظار القبض على الشوك. ما ليس جيدا ، لأنه ويمكن أيضا أن تنفق هذه المرة على التفكير. ليس من دون مقابل أن يكون هناك رأي مفاده أنه إذا فكرت في معدة فارغة ، يمكنك الحصول على نتائج أكثر إثارة للاهتمام وغير متوقعة ؛)
لنلقِ نظرة على أبسط الحلول التي يُرجع بها الفيلسوف أول شوكة تم التقاطها إذا لم يستطع التقاط الثانية (في مقالة "فلاسفة الطعام الحديث" المذكورة أعلاه ، يتم تطبيق هذا الحل بواسطة ForkLevelPhilosopherProtocol).
يمكن رؤية الكود المصدري لهذا التطبيق هنا ، والكود الخاص بممثل الفيلسوف المقابل هنا .
الرسائل
يستخدم هذا الحل نفس مجموعة الرسائل تقريبًا:
struct take_t { const so_5::mbox_t m_who; std::size_t m_philosopher_index; }; struct busy_t : public so_5::signal_t {}; struct taken_t : public so_5::signal_t {}; struct put_t : public so_5::signal_t {};
الفرق الوحيد هو وجود إشارة busy_t
. يرسل الممثل الفاعل هذه الإشارة استجابةً للفيلسوف - الممثل ، إذا كان الفيلسوف قد تم الاستيلاء عليها بالفعل.
الممثل شوكة
ممثل الشوكة في هذا الحل أبسط من حل Dijkstra:
class fork_t final : public so_5::agent_t { public : fork_t( context_t ctx ) : so_5::agent_t( ctx ) { this >>= st_free; st_free.event( [this]( mhood_t<take_t> cmd ) { this >>= st_taken; so_5::send< taken_t >( cmd->m_who ); } ); st_taken.event( []( mhood_t<take_t> cmd ) { so_5::send< busy_t >( cmd->m_who ); } ) .just_switch_to< put_t >( st_free ); } private : const state_t st_free{ this }; const state_t st_taken{ this }; };
نحن هنا لسنا بحاجة حتى إلى الحفاظ على صف الفلاسفة المنتظرين.
الفيلسوف الممثل
يشبه الفيلسوف - الفاعل في هذا التنفيذ ، ذلك الحل الخاص بـ Dijkstra ، ولكن هنا يجب على الفيلسوف - الفاعل أن يعالج busy_t
، لذلك يبدو مخطط الحالة كما يلي:

وبالمثل ، يتم تعريف المنطق الكامل للممثل الفيلسوف في so_define_agent()
:
void so_define_agent() override { st_thinking .event< stop_thinking_t >( [=] { this >>= st_wait_left; so_5::send< take_t >( m_left_fork, so_direct_mbox(), m_index ); } ); st_wait_left .event< taken_t >( [=] { this >>= st_wait_right; so_5::send< take_t >( m_right_fork, so_direct_mbox(), m_index ); } ) .event< busy_t >( [=] { think( st_hungry_thinking ); } ); st_wait_right .event< taken_t >( [=] { this >>= st_eating; } ) .event< busy_t >( [=] { so_5::send< put_t >( m_left_fork ); think( st_hungry_thinking ); } ); st_eating .on_enter( [=] { so_5::send_delayed< stop_eating_t >( *this, eat_pause() ); } ) .event< stop_eating_t >( [=] { so_5::send< put_t >( m_right_fork ); so_5::send< put_t >( m_left_fork ); ++m_meals_eaten; if( m_meals_count == m_meals_eaten ) this >>= st_done; else think( st_normal_thinking ); } ); st_done .on_enter( [=] { completion_watcher_t::done( so_environment(), m_index ); } ); }
بشكل عام ، هذا هو نفس الكود الموجود في حل Dijkstra ، باستثناء بضع معالجات لـ busy_t
.
النتائج
نتائج العمل تبدو مختلفة:
Socrates: tttttttttL..R.....EEEEEEEEEEEEttttttttttR...LL..EEEEEEEttEEEEEE Plato: ttttEEEEEEEEEEEttttttL.....L..EEEEEEEEEEEEEEEttttttttttL....L.... Aristotle: ttttttttttttL..LR.EEEEEEtttttttttttL..L....L....R.....EEEEEEEEE Descartes: ttttttttttEEEEEEEEttttttttttttEEEEEEEEttttEEEEEEEEEEEttttttL..L.. Spinoza: ttttttttttL.....L...EEEEEEtttttttttL.L......L....L..L...R...R...E Kant: tttttttEEEEEEEttttttttL.L.....EEEEEEEEttttttttR...R..R..EEEEEtttt Schopenhauer: tttR..R..L.....EEEEEEEttttttR.....L...EEEEEEEEEEEEEEEEttttttttttt Nietzsche: tttEEEEEEEEEEtttttttttEEEEEEEEEEEEEEEttttL....L...L..L....EEEEEEE Wittgenstein: tttttL.L..L.....RR....L.....L....L...EEEEEEEEEEEEEEEtttttttttL. Heidegger: ttttR..R......EEEEEEEEEEEEEttttttttttR..L...L...L..L...EEEEtttttt Sartre: tttEEEEEEEtttttttL..L...L....R.EEEEEEEtttttEEEEtttttttR.....R..R.
هنا نرى رمزًا جديدًا ، مما يعني أن الممثل الفيلسوف في "أفكار جائعة".
حتى في هذه القطعة القصيرة ، يمكن للمرء أن يرى أن هناك فترات طويلة من الزمن لا يستطيع الفيلسوف تناولها. هذا لأن هذا الحل محمي من مشكلة حالة توقف تام ، ولكنه لا يتمتع بحماية ضد الجوع.
القرار مع النادل وقائمة الانتظار
إن أبسط الحلول الموضحة أعلاه بدون حكم لا يحمي من الجوع. تحتوي مقالة "فلاسفة الطعام الحديثة" المذكورة أعلاه على حل لمشكلة الصيام في شكل بروتوكول WaiterFair. خلاصة القول هي أن هناك حكماً (نادل) ، يلجأ إليه الفلاسفة عندما يريدون تناول الطعام. والنادل لديه قائمة انتظار من الطلبات من الفلاسفة. ويحصل الفيلسوف على الشوك فقط إذا كان كلا الشوكين مجانيان الآن ، ولم يكن هناك أي من جيران الفيلسوف الذين تحولوا إلى النادل في قائمة الانتظار.
دعونا نلقي نظرة على كيف يمكن لهذا الحل نفسه أن ينظر إلى الجهات الفاعلة.
يمكن العثور على الكود المصدري لهذا التطبيق هنا .
خدعة
أسهل طريقة هي تقديم مجموعة جديدة من الرسائل التي من خلالها يمكن للفلاسفة التواصل مع النادل. لكنني أردت حفظ ليس فقط مجموعة الرسائل الموجودة بالفعل (مثل take_t
، taken_t
، busy_t
، put_t
). أردت أيضًا أن يتم استخدام نفس الفيلسوف الممثل كما في الحل السابق. لذلك ، كان عليّ حل مشكلة صعبة: كيف أجعل الممثل الفيلسوف يتواصل مع النادل الفاعل الوحيد ، لكن في الوقت نفسه اعتقد أنه يتفاعل مباشرة مع الممثلين - الشوك (التي لم تعد في الواقع).
تم حل هذه المشكلة باستخدام خدعة بسيطة: يقوم النادل الفاعل بإنشاء مجموعة من mbox-s ، يتم تقديم ارتباطات إلى الجهات الفاعلة للفيلسوف باعتبارها روابط إلى mbox- الجهات الفاعلة من المقابس. في نفس الوقت ، يشترك النادل الفاعل في الرسائل من جميع هذه mboxes (التي يتم تنفيذها بسهولة في SObjectizer ، لأن SObjectizer هو تطبيق ليس فقط / لأن العديد من نماذج الممثلين ، ولكن أيضًا Pub / Sub مدعوم من خارج الصندوق) .
في الكود ، يبدو كالتالي:
class waiter_t final : public so_5::agent_t { public : waiter_t( context_t ctx, std::size_t forks_count ) : so_5::agent_t{ std::move(ctx) } , m_fork_states( forks_count, fork_state_t::free ) {
أي أولاً ، قم بإنشاء متجه من mbox-s لـ "شوك" غير موجود ، ثم اشترك في كل منها. نعم ، نحن نشترك في معرفة المكونات المحددة التي يتصل بها الطلب.
المعالج الحقيقي لطلب on_take_fork()
الوارد هو الأسلوب on_take_fork()
:
void on_take_fork( mhood_t<take_t> cmd, std::size_t fork_index ) {
بالمناسبة ، كنا هنا نحتاج إلى الحقل الثاني من رسالة take_t
.
لذلك ، في on_take_fork()
لدينا الطلب الأصلي وفهرس الشوكة الذي يتعلق به الطلب. لذلك ، يمكننا تحديد ما إذا كان الفيلسوف يسأل عن مفترق يساري أم مفصل الأيمن. وبالتالي ، يمكننا معالجتها بشكل مختلف (وعلينا معالجتها بشكل مختلف).
نظرًا لأن الفيلسوف يسأل دائمًا عن الشوكة اليسرى ، فنحن بحاجة إلى القيام بكل الفحوصات اللازمة في هذه اللحظة بالذات. وقد نجد أنفسنا في أحد المواقف التالية:
- كلا الشوكين مجانيان ويمكن إعطاؤهما للفيلسوف الذي أرسل الطلب. في هذه الحالة ،
taken_t
الفيلسوف ، taken_t
علامة على الشوكة المناسبة على أنها محفوظة بحيث لا يمكن لأي شخص آخر أخذها. - لا يمكن إعطاء الشوك إلى الفيلسوف. لا يهم لماذا. ربما البعض منهم مشغولون الآن. أو في خط واحد من جيران الفيلسوف. على أي حال ، وضعنا الفيلسوف الذي أرسل الطلب في قائمة الانتظار ، وبعد ذلك
busy_t
إليه.
بفضل منطق العمل هذا ، يمكن للفيلسوف الذي حصل على taken_t
اليسرى أن يرسل بأمان طلب take_t
اليمنى. سيتم تلبية هذا الطلب فورًا الشوكة محجوز بالفعل لهذا الفيلسوف.
النتائج
إذا قمت بتشغيل الحل الناتج ، يمكنك رؤية شيء مثل:
Socrates: tttttttttttL....EEEEEEEEEEEEEEttttttttttL...L...EEEEEEEEEEEEEtttttL. Plato: tttttttttttL....L..L..L...L...EEEEEEEEEEEEEtttttL.....L....L.....EEE Aristotle: tttttttttL.....EEEEEEEEEttttttttttL.....L.....EEEEEEEEEEEtttL....LL Descartes: ttEEEEEEEEEEtttttttL.L..EEEEEEEEEEEEtttL..L....L....L.....EEEEEEEEEE Spinoza: tttttttttL.....EEEEEEEEEttttttttttL.....L.....EEEEEEEEEEEtttL....LL Kant: ttEEEEEEEEEEEEEtttttttL...L.....L.....EEEEEttttL....L...L..L...EEEEE Schopenhauer: ttttL...L.....L.EEEEEEEEEEEEEEEEEtttttttttttL..L...L..EEEEEEEttttttt Nietzsche: tttttttttttL....L..L..L...L...L.....L....EEEEEEEEEEEEttL.....L...L.. Wittgenstein: tttttttttL....L...L....L....L...EEEEEEEttttL......L.....L.....EEEEEE Heidegger: ttttttL..L...L.....EEEEEEEEEEEEtttttL...L..L.....EEEEEEEEEEEttttttL. Sartre: ttEEEEEEEEEEEEEttttttttL.....L...EEEEEEEEEEEEttttttttttttL.....EEEEE
يمكنك الانتباه إلى عدم وجود شخصيات R
وذلك لأن الإخفاقات أو التوقعات لا يمكن أن تحدث بناء على طلب شوكة مناسب.
قرار آخر باستخدام محكم (النادل)
في بعض الحالات ، قد يعرض حل waiter_with_queue السابق نتائج مشابهة لهذا واحد:
Socrates: tttttEEEEEEEEEEEEEEtttL.....LL...L....EEEEEEEEEttttttttttL....L.....EE Plato: tttttL..L..L....LL...EEEEEEEEEEEEEEEttttttttttttL.....EEEEEEEEEttttttt Aristotle: tttttttttttL..L...L.....L.....L....L.....EEEEEEEEEEEEtttttttttttL....L.. Descartes: ttttttttttEEEEEEEEEEttttttL.....L....L..L.....L.....L..L...L..EEEEEEEEtt Spinoza: tttttttttttL..L...L.....L.....L....L.....L..L..L....EEEEEEEEEEtttttttttt Kant: tttttttttL....L....L...L...L....L..L...EEEEEEEEEEEttttttttttL...L......E Schopenhauer: ttttttL....L..L...L...LL...L...EEEEEtttttL....L...L.....EEEEEEEEEttttt Nietzsche: tttttL..L..L....EEEEEEEEEEEEEttttttttttttEEEEEEEEEEEEEEEttttttttttttL... Wittgenstein: tttEEEEEEEEEEEEtttL....L....L..EEEEEEEEEtttttL..L..L....EEEEEEEEEEEEEEEE Heidegger: tttttttttL...L..EEEEEEEEttttL..L.....L...EEEEEEEEEtttL.L..L...L....L...L Sartre: ttttttttttL..L....L...L.EEEEEEEEEEEtttttL...L..L....EEEEEEEEEEtttttttttt
يمكنك أن ترى وجود فترات زمنية طويلة بما فيه الكفاية عندما لا يستطيع الفلاسفة تناول الطعام على الرغم من وجود شوكات مجانية. على سبيل المثال ، الشوكان الأيمن والأيسر لـ Kant مجانيان لفترة طويلة ، لكن كانط لا يستطيع أخذهما ، لأن جيرانه ينتظرون بالفعل في الطابور. التي تنتظر جيرانهم. الذين ينتظرون جيرانهم ، الخ
لذلك ، فإن تطبيق waiter_with_queue الذي نوقش أعلاه يحمي من الجوع بمعنى أن الفيلسوف سوف يأكل عاجلاً أم آجلاً. هذا مضمون له. لكن فترات الصيام يمكن أن تكون طويلة جدا. واستخدام الموارد قد لا يكون الأمثل في بعض الأحيان.
لحل هذه المشكلة ، قمت بتنفيذ حل آخر ، waiter_with_timestamp (يمكن العثور على الكود هنا ). بدلاً من الطابور ، يضعون أولويات طلبات الفلاسفة مع مراعاة وقت صيامهم. كلما طالت فترة بقاء الفيلسوف جائعًا ، كلما زادت أولوية طلبه.
لن نفكر في رمز هذا الحل لأن إلى حد كبير ، الشيء الرئيسي في ذلك هو نفس الخدعة مع مجموعة من صناديق mbox لـ "الشوك" غير الموجودة ، والتي ناقشناها بالفعل في المحادثة حول تنفيذ waiter_with_queue.
بعض تفاصيل التنفيذ التي أود لفت الانتباه إليها
هناك العديد من التفاصيل في التطبيقات بناءً على الممثلين الذين أود الانتباه إليهم ، لأنه هذه التفاصيل توضح ميزات مثيرة للاهتمام من SObjectizer.
سياق العمل للجهات الفاعلة
في fork_t
، عملت جميع الجهات الفاعلة الرئيسية ( fork_t
، philosopher_t
، waiter_t
) على سياق سلسلة عمليات مشتركة واحدة. هذا لا يعني على الإطلاق أنه في SObjectizer ، تعمل جميع الجهات الفاعلة على موضوع واحد فقط. في SObjectizer ، يمكنك ربط الجهات الفاعلة بسياقات مختلفة ، والتي يمكن رؤيتها ، على سبيل المثال ، في رمز run_simulation()
في الحل no_waiter_simple.
Run_simulation code from no_waiter_simple void run_simulation( so_5::environment_t & env, const names_holder_t & names ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent_with_binder< trace_maker_t >( so_5::disp::one_thread::create_private_disp( env )->binder(), names, random_pause_generator_t::trace_step() ); coop.make_agent_with_binder< completion_watcher_t >( so_5::disp::one_thread::create_private_disp( env )->binder(), names ); const auto count = names.size(); std::vector< so_5::agent_t * > forks( count, nullptr ); for( std::size_t i{}; i != count; ++i ) forks[ i ] = coop.make_agent< fork_t >(); for( std::size_t i{}; i != count; ++i ) coop.make_agent< philosopher_t >( i, forks[ i ]->so_direct_mbox(), forks[ (i + 1) % count ]->so_direct_mbox(), default_meals_count ); }); }
في هذه الوظيفة ، completion_watcher_t
عناصر إضافية من الأنواع trace_maker_t
و trace_maker_t
. سوف يعملون على سياقات العمل الفردية. للقيام بذلك ، يتم one_thread
من المرسل من نوع one_thread
هذه. مما يعني أن هذه الجهات الفاعلة ستعمل ككائنات نشطة : كل منها سوف يمتلك سلسلة العمل الخاصة به.
يوفر SObjectizer مجموعة من العديد من المرسلين المختلفين الذين يمكن استخدامهم خارج الصندوق مباشرة. في هذه الحالة ، يمكن للمطور إنشاء في تطبيقه العديد من مثيلات المرسلين التي يحتاجها المطور.
, , . , fork_t
, philosopher_t
.
run_simulation no_waiter_simple_tp void run_simulation( so_5::environment_t & env, const names_holder_t & names ) { env.introduce_coop( [&]( so_5::coop_t & coop ) { coop.make_agent_with_binder< trace_maker_t >( so_5::disp::one_thread::create_private_disp( env )->binder(), names, random_pause_generator_t::trace_step() ); coop.make_agent_with_binder< completion_watcher_t >( so_5::disp::one_thread::create_private_disp( env )->binder(), names ); const auto count = names.size();
fork_t
philosopher_t
.
Modern dining philosophers , , :
void doEat() { eventLog_.startActivity(ActivityType::eat); wait(randBetween(10, 50)); eventLog_.endActivity(ActivityType::eat);
SObjectizer . , , . بسبب ماذا؟
, SObjectizer- : . agent_state_listener_t
. , SObjectizer .
greedy_philosopher_t
philosopher_t
:
philosopher_t(...) ... { so_add_destroyable_listener( state_watcher_t::make( so_environment(), index ) ); }
state_watcher_t
— .
state_watcher_t class state_watcher_t final : public so_5::agent_state_listener_t { const so_5::mbox_t m_mbox; const std::size_t m_index; state_watcher_t( so_5::mbox_t mbox, std::size_t index ); public : static auto make( so_5::environment_t & env, std::size_t index ) { return so_5::agent_state_listener_unique_ptr_t{ new state_watcher_t{ trace_maker_t::make_mbox(env), index } }; } void changed( so_5::agent_t &, const so_5::state_t & state ) override; };
state_watcher_t
SObjectizer changed()
. state_watcher_t::changed
-.
state_watcher_t::changed void state_watcher_t::changed( so_5::agent_t &, const so_5::state_t & state ) { const auto detect_label = []( const std::string & name ) {...}; const char state_label = detect_label( state.query_name() ); if( '?' == state_label ) return; so_5::send< trace::state_changed_t >( m_mbox, m_index, state_label ); }
CSP
, . (no_waiter_dijkstra, no_waiter_simple, waiter_with_timestamps) std::thread
SObjectizer- mchain- (, , CSP- ). , , CSP- ( take_t
, taken_t
, busy_t
, put_t
).
CSP- "" . , std::thread
.
.
: + take_t
put_t
. fork_process
:
void fork_process( so_5::mchain_t fork_ch ) {
fork_process
: , - .
fork_process
— "" , . receive()
:
so_5::receive( so_5::from( fork_ch ), [&]( so_5::mhood_t<take_t> cmd ) {...}, [&]( so_5::mhood_t<put_t> ) {...} );
SObjectizer- receive()
. . . , . , .
-. fork_t
. , , .
philosopher_process
. , .
philosopher_process oid philosopher_process( trace_maker_t & tracer, so_5::mchain_t control_ch, std::size_t philosopher_index, so_5::mbox_t left_fork, so_5::mbox_t right_fork, int meals_count ) { int meals_eaten{ 0 }; random_pause_generator_t pause_generator;
:
void philosopher_process( trace_maker_t & tracer, so_5::mchain_t control_ch, std::size_t philosopher_index, so_5::mbox_t left_fork, so_5::mbox_t right_fork, int meals_count )
.
SObjectizer- , , Actor-. :
tracer.thinking_started( philosopher_index, thinking_type_t::normal );
tracer
, .
control_ch
, philosopher_done_t
, , . .
left_fork
right_fork
. take_t
put_t
. , mbox_t
mchain_t
?
! , . , mchain — - mbox-, mchain- mbox_t
.
, :
int meals_eaten{ 0 }; random_pause_generator_t pause_generator; auto self_ch = so_5::create_mchain( control_ch->environment() );
— self_ch
. , .
. أي , .
, , this_thread::sleep_for
.
, :
so_5::send< take_t >( left_fork, self_ch->as_mbox(), philosopher_index );
take_t
. mbox_t
, self_ch
mchain_t
. as_mbox()
.
receive()
:
so_5::receive( so_5::from( self_ch ).handle_n( 1u ), [&]( so_5::mhood_t<taken_t> ) {...} );
taken_t
. . , .
- , philosopher_process
. receive()
:
so_5::receive( so_5::from( self_ch ).handle_n( 1u ), [&]( so_5::mhood_t<taken_t> ) { ... so_5::receive( so_5::from( self_ch ).handle_n( 1u ), [&]( so_5::mhood_t<taken_t> ) {...} ); ... } );
- .
run_simulation()
, . CSP- run_simulation()
. , , ( ).
run_simulation void run_simulation( so_5::environment_t & env, const names_holder_t & names ) noexcept { const auto table_size = names.size(); const auto join_all = []( std::vector<std::thread> & threads ) { for( auto & t : threads ) t.join(); }; trace_maker_t tracer{ env, names, random_pause_generator_t::trace_step() };
, run_simulation()
- . .
. :
std::vector< so_5::mchain_t > fork_chains; std::vector< std::thread > fork_threads( table_size ); for( std::size_t i{}; i != table_size; ++i ) { fork_chains.emplace_back( so_5::create_mchain(env) ); fork_threads[ i ] = std::thread{ fork_process, fork_chains.back() }; }
, , . . , join
.
, .. join
:
std::vector< std::thread > philosopher_threads( table_size ); for( std::size_t i{}; i != table_size - 1u; ++i ) { philosopher_threads[ i ] = philosopher_maker( i, i, i+1u ); } philosopher_threads[ table_size - 1u ] = philosopher_maker( table_size - 1u, table_size - 1u, 0u );
. :
so_5::receive( so_5::from( control_ch ).handle_n( table_size ), [&names]( so_5::mhood_t<philosopher_done_t> cmd ) { fmt::print( "{}: done\n", names[ cmd->m_philosopher_index ] ); } );
receive()
table_size
philosopher_done_t
.
philosopher_done_t
.
join
:
join_all( philosopher_threads );
join
. join
, .. . receive()
. join
:
for( auto & ch : fork_chains ) so_5::close_drop_content( ch ); join_all( fork_threads );
.
noexcept
, run_simulation
, noexcept . , exception-safety . — .
run_simulation
?
, . - exception-safety , . - , :
try { for( std::size_t i{}; i != table_size; ++i ) { fork_chains.emplace_back( so_5::create_mchain(env) ); fork_threads[ i ] = std::thread{ fork_process, fork_chains.back() }; } } catch( ... ) { for( std::size_t i{}; i != fork_chains.size(); ++i ) { so_5::close_drop_content( fork_chains[ i ] ); if( fork_threads[ i ].joinable() ) fork_threads[ i ].join(); } throw; }
, . بسبب , . - :
struct fork_threads_stuff_t { std::vector< so_5::mchain_t > m_fork_chains; std::vector< std::thread > m_fork_threads; fork_threads_stuff_t( std::size_t table_size ) : m_fork_threads( table_size ) {} ~fork_threads_stuff_t() { for( std::size_t i{}; i != m_fork_chains.size(); ++i ) { so_5::close_drop_content( m_fork_chains[ i ] ); if( m_fork_threads[ i ].joinable() ) m_fork_threads[ i ].join(); } } void run() { for( std::size_t i{}; i != m_fork_threads.size(); ++i ) { m_fork_chains.emplace_back( so_5::create_mchain(env) ); m_fork_threads[ i ] = std::thread{ fork_process, m_fork_chains.back() }; } } } fork_threads_stuff{ table_size };
, (, Boost- ScopeExit-, GSL- finally() ).
. .
, exception-safety run_simulation()
, run_simulation()
, . , -. exception-safety run_simulation()
noexcept , std::terminate
. , .
, , , , . , join
, join
. .
()
, CSP- , .
.
fork_process
, :
void fork_process( so_5::mchain_t fork_ch ) {
, fork_process
, ( , ).
philosopher_process
, , .
philosopher_process void philosopher_process( trace_maker_t & tracer, so_5::mchain_t control_ch, std::size_t philosopher_index, so_5::mbox_t left_fork, so_5::mbox_t right_fork, int meals_count ) { int meals_eaten{ 0 };
- philosopher_process
philosopher_process
. .
-, thinking_type
. , , , "" .
-, busy_t
. receive()
:
so_5::receive( so_5::from( self_ch ).handle_n( 1u ), []( so_5::mhood_t<busy_t> ) { }, [&]( so_5::mhood_t<taken_t> ) { ... so_5::receive( so_5::from( self_ch ).handle_n( 1u ), []( so_5::mhood_t<busy_t> ) { }, [&]( so_5::mhood_t<taken_t> ) {...} );
, busy_t
, , receive()
, receive()
. busy_t
. , .. receive()
busy_t
. receive()
busy_t
.
CSP- , . (): waiter_with_queue, , waiter_with_timestamps. : mbox- , mbox- , mbox- .
CSP- , philosopher_process
no_waiter_simple. mchain- , ?
, .
mchain- . , mchain-.
SObjectizer- select()
, , , :
so_5::select( so_5::from_all(), case_(ch1, one_handler_1, one_handler_2, one_handler_3, ...), case_(ch2, two_handler_1, two_handler_2, two_handler_3, ...), ...);
select()
, -. " " . CSP- .
.
, , take_t
put_t
. - . take_t
put_t
, :
struct extended_take_t final : public so_5::message_t { const so_5::mbox_t m_who; const std::size_t m_philosopher_index; const std::size_t m_fork_index; extended_take_t( so_5::mbox_t who, std::size_t philosopher_index, std::size_t fork_index ) : m_who{ std::move(who) } , m_philosopher_index{ philosopher_index } , m_fork_index{ fork_index } {} }; struct extended_put_t final : public so_5::message_t { const std::size_t m_fork_index; extended_put_t( std::size_t fork_index ) : m_fork_index{ fork_index } {} };
, so_5::message_t
, ( ). , SObjectizer- .
, . take_t
put_t
, extended_take_t
extended_put_t
, .
mbox. :)
mbox- class wrapping_mbox_t final : public so_5::extra::mboxes::proxy::simple_t { using base_type_t = so_5::extra::mboxes::proxy::simple_t;
mbox-: so_5_extra , . so_5::abstract_message_box_t
.
, wrapping_mbox_t
. , . wrapping_mbox, mchain . waiter_process
, , :
void waiter_process( so_5::mchain_t waiter_ch, details::waiter_logic_t & logic ) {
, , . waiter_with_timestamps .
: " philosopher_process
mbox-?" , waiter_with_timestamps mbox, mchain.
, mchain. , .. so_5_extra mchain- ( ). mbox- mchain-.
الخاتمة
, , , CSP . , . , , . , - .
, SObjectizer-. , "" SObjectizer — 5.6, 5.5. , ( ). - , SO-5.6 ( ).
, !
PS. "" , . C++14.