在下面的代码中,我希望输出始终为 1,因为我希望在以下情况下仅运行一个处理程序:poll_one()
叫做。然而,一旦大约 300 次,输出实际上是 3。根据我对 boost 库的理解,这似乎不正确。不确定性行为是错误还是预期行为?
#include <boost/asio.hpp>
int main() {
boost::asio::io_service io;
boost::asio::io_service::work io_work(io);
boost::asio::io_service::strand strand1(io);
boost::asio::io_service::strand strand2(io);
int val = 0;
strand1.post([&val, &strand2]() {
val = 1;
strand2.post([&val]() {
val = 2;
});
boost::asio::spawn(strand2, [&val](boost::asio::yield_context yield) {
val = 3;
});
});
io.poll_one();
std::cout << "Last executed: " << val << std::endl;
return 0;
}
使用boost-asio 1.60.0.6
观察到的行为是明确定义的并且预期会发生,但人们不应期望它经常发生。
Asio 的链实现池有限,链的默认分配策略是哈希。如果发生哈希冲突,两个链将使用相同的实现。当发生哈希冲突时,示例简化为以下内容demo http://coliru.stacked-crooked.com/a/82c283c7a8388d52:
#include <cassert>
#include <boost/asio.hpp>
int main()
{
boost::asio::io_service io_service;
boost::asio::io_service::strand strand1(io_service);
// Have strand2 use the same implementation as strand1.
boost::asio::io_service::strand strand2(strand1);
int value = 0;
auto handler1 = [&value, &strand1, &strand2]() {
assert(strand1.running_in_this_thread());
assert(strand2.running_in_this_thread());
value = 1;
// handler2 is queued into strand and never invoked.
auto handler2 = [&value]() { assert(false); };
strand2.post(handler2);
// handler3 is immediately executed.
auto handler3 = [&value]() { value = 3; };
strand2.dispatch(handler3);
assert(value == 3);
};
// Enqueue handler1.
strand1.post(handler1);
// Run the event processing loop, executing handler1.
assert(io_service.poll_one() == 1);
}
在上面的例子中:
-
io_service.poll_one()
执行单个就绪处理程序(handler1
)
-
handler2
从未被调用
-
handler3
立即被调用strand2.dispatch() http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service__strand/dispatch.html, as strand2.dispatch()
从处理程序中调用,其中strand2.running_in_this_thread()
回报true
观察到的行为有多种细节:
io_service::poll_one() http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service/poll_one/overload1.html将运行io_service
的事件循环并且没有阻塞,它将最多执行一个准备运行的处理程序。处理程序在上下文中立即执行dispatch()
永远不会排队到io_service
,并且不受poll_one()
调用单个处理程序的限制。
-
The boost::asio::spawn(strand, function) http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/spawn/overload3.html过载启动一个堆栈协程as-if by strand.dispatch()
:
- if
strand.running_in_this_thread()
回报false
对于调用者,那么协程将被发布到strand
用于延迟调用
- if
strand.running_in_this_thread()
回报true
对于调用者,那么协程将立即执行
-
离散的strand
使用相同实现的对象仍然保持链的保证。即,不会发生并发执行,并且处理程序调用顺序 http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service__strand.html#boost_asio.reference.io_service__strand.order_of_handler_invocation是明确定义的。当离散时strand
对象使用离散实现,并且多个线程正在运行io_service
,那么我们可以观察到离散的线程同时执行。然而,当离散strand
对象使用相同的实现,即使多个线程正在运行,也不会观察到并发性io_service
。这种行为是有记录的 http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service__strand.html#boost_asio.reference.io_service__strand.remarks:
该实现不保证通过不同链对象发布或分派的处理程序将被同时调用。
-
Asio 的链实现池有限。当前默认值是193
并且可以通过定义来控制BOOST_ASIO_STRAND_IMPLEMENTATIONS
到所需的数量。此功能在Boost.Asio 1.48 发行说明 http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/history.html#boost_asio.history.asio_1_6_1___boost_1_48
通过定义可配置链实现的数量BOOST_ASIO_STRAND_IMPLEMENTATIONS
到所需的数量。
通过减小池大小,可以增加两个离散链使用相同实现的机会。使用原始代码,如果将池大小设置为1
, then strand1
and strand2
将始终使用相同的实现,从而导致val
总是存在3
(demo http://coliru.stacked-crooked.com/a/d98ac182372d4192).
-
分配链实现的默认策略是使用黄金比例哈希。由于使用了散列算法,因此存在潜在的冲突,导致相同的实现被用于多个离散的strand
对象。通过定义BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION
,可以将分配策略更改为循环,以防止发生冲突,直到BOOST_ASIO_STRAND_IMPLEMENTATIONS + 1
发生了链分配。 Boost.Asio 1.48 发行说明中提到了此功能:
添加了对新的支持BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION
标志,它将链实现的分配切换为使用循环方法而不是散列。
鉴于上述详细信息,当以下情况发生时1
在原始代码中观察到:
另一方面,当3
观察到:
- 发生哈希冲突
strand1
and strand2
,导致它们使用相同的底层链实现
-
io_service::poll_one()
执行直接发布到的单个处理程序strand1
- 发布到的处理程序
strand1
sets val
to 1
- 处理程序发布到
strand2
已排队且从未被调用
- 协程立即被创建并被调用
boost::asio::spawn()
, 环境val
to 3
, as strand2
可以安全地执行协程,同时保持非并发执行的保证和处理程序调用的顺序
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)