不久前,指向“现代餐饮哲学家”的文章的链接遍布Reddit和HackerNews等资源。 这篇文章很有趣,它展示了使用基于任务的方法在现代C ++中实现的这项众所周知的任务的几种解决方案。 如果还没有人读过这篇文章,那么花时间去阅读它是有意义的。
但是,我不能说本文中介绍的解决方案对我来说似乎很简单并且可以理解。 这可能是由于使用任务。 它们太多,是通过各种分派器/序列化器创建和分派的。 因此,并不总是清楚在何处,何时以及执行什么任务。
此外,基于任务的方法不是解决此类问题的唯一一种方法。 为什么不看看通过Actor和CSP的模型如何解决“用餐哲学家”的任务呢?
因此,我尝试使用Actor和CSP查找并实施了针对此问题的几种解决方案。 这些解决方案的代码可以在GitHub的存储库中找到 。 并在刀具的下方进行了解释和说明,因此,任何有兴趣的人,请随时关注。
一些常用词
我的目标不是要完全重复“现代餐饮哲学家”一文中显示的决定,尤其是因为我从根本上不喜欢一件重要的事情:实际上,哲学家对这些决定不做任何事情。 他只说“我要吃”,然后有人神奇地给了他叉子,或者说“现在不行了”。
很明显,作者为什么要诉诸这种行为:它允许将“哲学家”的相同实现与“协议”的不同实现结合使用。 但是,在我个人看来,当“哲学家”尝试先插一个插头再插另一个插头时,这更有趣。 并且当“哲学家”被迫处理不成功的尝试来获取叉子时。
我正是试图做到的这些“进餐哲学家”任务的实现。 同时,某些解决方案使用与上述文章相同的方法(例如,由ForkLevelPhilosopherProtocol和WaiterFair协议实现)。
我基于SObjectizer做出了决定,这不太可能使以前阅读过我的文章的人感到惊讶。 简而言之,如果还没有人听说过SObjectizer:这是为C ++开发的少数几个现成的,正在开发的OpenSource“角色框架”之一(也可以提及CAF和QP / C ++ )。 我希望上面的例子和我的评论即使对于不熟悉SObjectizer的人也足够清楚。 如果没有,我将很乐意在评论中回答问题。
演员解决方案
我们将开始与基于Actor的解决方案进行讨论。 首先,考虑Edsger Dijkstra解决方案的实现,然后继续研究其他几个解决方案,并查看每个解决方案的行为如何不同。
迪克斯特拉的决定
Edsger Dijkstra,他不仅制定了“用餐生菜”的任务(托尼·霍尔(Tony Hoar)曾用“叉子”和“意大利面条”来拟定它),还提出了一个非常简单而优美的解决方案。 即:哲学家只应按增加叉子数量的顺序抓住叉子,如果哲学家设法拿起了第一把叉子,他将不会松手直到收到第二把叉子。
例如,如果哲学家需要使用数字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
消息发送给fork take_t
,并且fork actor会以taken_t
消息进行响应。 当演员哲学家吃完饭并想把叉子放回桌子上时,他将put_t消息发送给put_t
叉子。
在take_t
消息中, take_t
字段表示哲学家演员的邮箱(aka 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中的每个actor必须从基类agent_t
。 我们在这里看到的类型为fork_t
。
在so_define_agent()
类中重写了so_define_agent()
方法。 这是一种特殊的方法,在注册新代理时SObjectizer会自动调用它。 在so_define_agent()
方法中,将so_define_agent()
“配置”为在SObjectizer中工作:启动状态更改,必要的消息已订阅。
SObjectizer中的每个参与者都是具有状态的状态机(即使参与者仅使用一个默认状态)。 fork_t
actor具有两种状态: free和fork_t
。 当演员处于自由状态时,插头可以被哲学家“俘获”。 并且在捕获了“ fork”之后, fork_t
actor应该进入fork_t
状态。 在fork_t
类内部fork_t
状态由特殊state_t
类型的st_free
和st_taken
实例表示。
状态允许您以不同方式处理传入消息。 例如,在自由状态下,代理仅对take_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
状态下,actor处理两条消息,甚至take_t
message 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
最有趣:如果正在等待的哲学家的队列为空,那么我们可以返回free ,但是如果它不为空,则需要将其第一个发送给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计时器,计时器在时间到时将消息传递给Actor。
使用延迟消息使您可以在单个工作线程的上下文中运行所有参与者。 粗略地说,一个线程从某个队列中读取消息,并从相应的接收者参与者中提取下一个消息处理程序。 有关演员的工作环境的更多信息将在下面讨论。
结果
此实现的结果可能如下所示(一个小片段):
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
表示哲学家“吃”。
我们可以看到,只有在萨特(Sartre)放下叉子后,苏格拉底才能把叉子放在左边。 之后,苏格拉底将等到柏拉图松开右叉。 这样苏格拉底才能吃东西。
没有仲裁员(服务员)的简单决定
如果我们分析迪克斯特拉决定的结果,我们将看到哲学家花费大量时间等待叉子的捕获。 什么不好,因为 这段时间也可以花在反思上。 并非没有道理的意见是,如果您空腹思考,您会得到更多有趣和意想不到的结果;)
让我们看一下最简单的解决方案,其中如果哲学家无法捕获第二个叉子,则他将返回第一个被捕获的叉子(在上述文章“现代餐饮哲学家”中,此解决方案由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
信号的存在。 如果叉子已经被另一位哲学家捕获,则actor-fork发送此信号以响应该哲学家-actor。
演员叉
此解决方案中的fork actor比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
)。 我还希望使用与先前解决方案中相同的演员哲学家。 因此,我不得不解决一个棘手的问题:如何使演员哲学家与唯一的演员侍者交流,但同时又认为他直接与演员叉(已经消失)进行交互。
使用一个简单的技巧就解决了这个问题:一个actor-waiter创建了一组mbox-s,与之链接的是链接到Philosopher-actor的fork actor的mbox-s。 同时,actor-waiter订阅了来自所有这些mbox的消息(在SObjectizer中很容易实现,因为SObjectizer不仅实现了/尽可能多的Actor模型,而且还提供了开箱即用的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()
我们有原始请求以及该请求on_take_fork()
相关的fork的索引。 因此,我们可以确定哲学家是要求左叉还是右叉。 并且,因此,我们可以对它们进行不同的处理(并且我们必须对它们进行不同的处理)。
由于哲学家总是首先要求左叉,因此我们此时需要进行所有必要的检查。 并且我们可能会发现自己处于以下情况之一:
- 两把叉子都是免费的,可以交给发送请求的哲学家。 在这种情况下,我们
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
这是因为在正确的fork请求上不会发生失败或期望。
使用仲裁员(服务员)的另一项决定
在某些情况下,先前的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
您会看到存在足够长的时间,即使存在免费叉子,哲学家也无法进食。 例如,康德的左,右叉子很长一段时间都是免费的,但康德不能带走它们,因为 他的邻居已经在排队等候。 哪些正在等待他们的邻居。 谁在等邻居
因此,上述讨论的waiter_with_queue的实现可以防止饥饿,从某种意义上说,哲学家迟早会吃东西。 这是对他的保证。 但是禁食期可能会很长。 而且资源利用率有时可能不是最佳的。
为了解决这个问题,我实现了另一个解决方案waiter_with_timestamp(可以在此处找到其代码)。 他们没有排队,而是考虑禁食时间来优先考虑哲学家的请求。 哲学家饿得越久,他的要求就越优先。
我们将不考虑此解决方案的代码,因为 总的来说,它的主要问题是使用一组用于不存在的“ forks”的mbox的相同技巧,我们已经在对话中讨论了waiter_with_queue的实现。
我想引起注意的一些实施细节
我想关注基于Actor的实现中的一些细节,因为 这些细节展示了SObjectizer的有趣功能。
演员的工作环境
在考虑的实现中,所有主要参与者( fork_t
, philosopher_t
, waiter_t
)都在一个公共工作线程的上下文中工作。 这根本不意味着在SObjectizer中所有参与者仅在一个线程上工作。 在SObjectizer中,您可以将角色绑定到不同的上下文,例如,可以在解决方案no_waiter_simple中的功能代码run_simulation()
中看到。
来自no_waiter_simple的Run_simulation代码 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
和completion_watcher_t
类型的其他参与者。 他们将根据个人工作环境工作。 为此, one_thread
了一个one_thread
类型的调度程序的两个实例,并将actor绑定到这些调度程序的实例。 这意味着这些参与者将充当活动对象 :每个参与者将拥有自己的工作线程。
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.