io_service::p oll_one 非确定性行为
io_service::poll_one non-deterministic behaviour
在下面的代码中,我希望输出始终为 1,因为我预计在调用 poll_one()
时只有一个处理程序运行。但是,大约 300 次一次,输出实际上是 3。根据我对提升库的理解,这似乎是不正确的。非确定性行为是错误还是预期?
#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 的链实现池有限,链的默认分配策略是散列。 如果发生哈希冲突,两条链将使用相同的实现。 发生哈希冲突时,该示例简化为以下演示:
#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()
内立即调用,因为strand2.dispatch()
是从处理程序中调用的,strand2.running_in_this_thread()
返回true
有各种细节会导致观察到的行为:
io_service::poll_one()
将运行io_service
的事件循环,并且没有阻塞,它将最多执行一个准备运行的处理程序。 在dispatch()
上下文中立即执行的处理程序永远不会排队进入io_service
,并且不受poll_one()
调用单个处理程序的限制。boost::asio::spawn(strand, function)
重载启动堆叠协程,如下所示strand.dispatch()
:- 如果
strand.running_in_this_thread()
返回调用方的false
,则协程将发布到strand
以进行延迟调用 - 如果
strand.running_in_this_thread()
返回调用方的true
,则协程将立即执行
- 如果
使用相同实现的离散
strand
对象仍保持链的保证。 也就是说,不会发生并发执行,并且处理程序调用的顺序已明确定义。 当离散strand
对象使用离散实现,并且多个线程正在运行io_service
时,可以观察到离散链并发执行。 但是,当离散strand
对象使用相同的实现时,即使多个线程正在运行io_service
,也不会观察到并发性。 记录了此行为:该实现不保证通过不同链对象发布或调度的处理程序将被并发调用。
Asio有一个有限的链实现池。 当前默认值为
。193
,可以通过将BOOST_ASIO_STRAND_IMPLEMENTATIONS
定义为所需数字来控制。 此功能在 Boost.Asio 1.48 发行说明中有所说明通过将
BOOST_ASIO_STRAND_IMPLEMENTATIONS
定义为所需数量,使链实现的数量可配置。通过减小池大小,可以增加两个离散链使用相同的实现的机会。 使用原始代码,如果要将池大小设置为
1
,则strand1
和strand2
将始终使用相同的实现,从而导致val
始终3
(演示(。分配链实现的默认策略是使用黄金比率哈希。 使用散列算法时,可能会发生冲突,从而导致对多个离散
strand
对象使用相同的实现。 通过定义BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION
,可以将分配策略更改为循环,防止发生冲突,直到BOOST_ASIO_STRAND_IMPLEMENTATIONS + 1
链分配发生。 此功能在 Boost.Asio 1.48 发行说明中有所说明:添加了对新
BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION
标志的支持,该标志将链实现的分配切换为使用循环方法而不是哈希。
鉴于上述详细信息,在原始代码中观察到1
时,将发生以下情况:
-
strand1
和strand2
具有离散的实现 -
io_service::poll_one()
执行直接发布到strand1
的单个处理程序 - 发布到
strand1
集合中的处理程序val
1
- 发布到
strand2
中的处理程序已排队且永远不会调用 协程的创建是延迟的,因为
strand
的调用顺序保证会阻止在发布到strand2
的上一个处理程序执行之前创建协程:给定一个链对象
s
,如果s.post(a)
发生在s.dispatch(b)
之前,后者在链外执行,那么asio_handler_invoke(a1, &a1)
发生在asio_handler_invoke(b1, &b1)
之前。
另一方面,当观察到3
时:
strand1
和strand2
发生哈希冲突,导致它们使用相同的底层链实现-
io_service::poll_one()
执行直接发布到strand1
的单个处理程序 - 发布到
strand1
集中的处理程序val
1
- 发布到
strand2
中的处理程序已排队且从未调用 - 协程立即在
boost::asio::spawn()
内创建和调用,val
设置为3
,因为strand2
可以安全地执行协程,同时保持非并发执行和处理程序调用顺序的保证
- 更改由unique_ptr指向的指针的指针是不确定的行为吗?
- 此代码,为什么必须显示不确定的行为
- 为什么在const和非const方法中删除代码重复不是不确定的行为
- 通过指向班级第一个成员的指针访问成员是不确定的行为吗?
- IS是INT_MAX (-1)不确定的行为
- 通过全球常数对静态数据成员的初始化会导致不确定的行为
- 更改对象并将其在同一表达式中使用它是不确定的行为,但是子表达式由逗号运算符分开
- 不确定的行为通过确定主义程序
- STD :: Regex不确定的行为
- 阅读和比较POD类型的填充字节是不确定的行为吗?
- 当constexpr评估负面bitshift时,不确定的行为
- 如果无限循环在C 中仍然不确定的行为,如果它调用共享库
- 在初始评估列表中引用类成员是不确定的行为吗?
- 读取文件和不确定的行为
- 产卵儿童和执行 - 不确定的行为
- 两条未签名短裤的乘法是否真的会导致不确定的行为
- 在其成员函数返回之前,将对象删除是不确定的行为吗?
- 是这种减少的测序调用不确定的行为
- 为什么常数表达式对不确定的行为有排除
- C 可能不确定的行为会导致调试和释放构建行为不同