Mic*_*rek 13 c++ boost boost-asio
在下面的代码中,我希望输出始终为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;
}
Run Code Online (Sandbox Code Playgroud)
使用boost-asio 1.60.0.6
Tan*_*ury 14
观察到的行为已经明确定义并且预计会发生,但人们不应该预期它会经常发生.
Asio有一个有限的链实现池,而strands的默认分配策略是散列.如果发生哈希冲突,则两个链将使用相同的实现.发生哈希冲突时,该示例简化为以下演示:
#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);
}
Run Code Online (Sandbox Code Playgroud)
在上面的例子中:
io_service.poll_one()执行一个就绪处理程序(handler1)handler2 永远不会被调用handler3在内部调用strand2.dispatch(),就像strand2.dispatch()从strand2.running_in_this_thread()返回的处理程序中调用一样true有各种细节有助于观察到的行为:
io_service::poll_one()将运行io_service事件循环而不阻塞,它将执行最多一个准备运行的处理程序.在a的上下文中立即执行的处理程序dispatch()永远不会入队io_service,并且不受poll_one()限于调用单个处理程序的限制.
在boost::asio::spawn(strand, function)过载开始stackful协程作为假设通过strand.dispatch():
strand.running_in_this_thread()返回false调用者,那么协程将被发布到strandfor deferred调用中strand.running_in_this_thread()返回true调用者,则会立即执行协程strand使用相同实现的离散对象仍然保持链的保证.即,不会发生并发执行,并且很好地定义了处理程序调用的顺序.当离散strand对象使用离散实现,并且多个线程正在运行时io_service,则可以观察到并行执行的离散链.但是,当离散strand对象使用相同的实现时,即使多个线程正在运行,也不会观察到并发性io_service.这种行为被记录:
该实现不保证将同时调用通过不同strand对象发布或分派的处理程序.
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() 执行直接发布到的单个处理程序 strand1strand1集合val到1strand2已入队并且从未被调用过协程创建是延迟的,因为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() 执行直接发布到的单个处理程序 strand1strand1集合val到1strand2已入队并且从未被调用过boost::asio::spawn(),设置val为3,strand2可以安全地执行协程,同时保持非并发执行的保证和处理程序调用的顺序