asio/strand:为什么计时器的行为不同?
我一直在关注这个 优秀的asio教程,但是对strands的具体作用感到困惑。我的理解是,它们的工作方式就像一组处理程序(回调)的队列,这样队列中的处理程序将按顺序执行。但一些实验表明我错了。有人能解释一下它们到底是什么吗?
我从 示例 6c 它执行 PrintNum(1)
到 PrintNum(5)
,每次延迟 1 秒,然后触发计时器。 (不直观的是,如果我将 PrintNum 调用移至启动计时器后,也会发生这种情况!然后我意识到调用 TimerHandler 的请求在计时器触发之前不会进入链队列。)
我的第一个变体的方法是仅删除计时器上的链引用,但将它们保留在 PrintNum 上(查看要点上的完整代码) :
strand->post( boost::bind( &PrintNum, 1 ) );
strand->post( boost::bind( &PrintNum, 2 ) );
strand->post( boost::bind( &PrintNum, 3 ) );
strand->post( boost::bind( &PrintNum, 4 ) );
strand->post( boost::bind( &PrintNum, 5 ) );
boost::shared_ptr< boost::asio::deadline_timer > timer(
new boost::asio::deadline_timer( *io_service )
);
timer->expires_from_now( boost::posix_time::seconds( 1 ) );
timer->async_wait( boost::bind( &TimerHandler, _1, timer ) );
现在计时器的运行独立于 PrintNum 调用。我得到了我期望的输出。
我的问题来自我的第二个变体(参见要点),其中我删除了对 PrintNum 的链调用,但保留了它们在计时器上:(
io_service->post( boost::bind( &PrintNum, 1 ) );
io_service->post( boost::bind( &PrintNum, 2 ) );
io_service->post( boost::bind( &PrintNum, 3 ) );
io_service->post( boost::bind( &PrintNum, 4 ) );
io_service->post( boost::bind( &PrintNum, 5 ) );
boost::shared_ptr< boost::asio::deadline_timer > timer(
new boost::asio::deadline_timer( *io_service )
);
timer->expires_from_now( boost::posix_time::milliseconds( 1000 ) );
timer->async_wait(
strand->wrap( boost::bind( &TimerHandler, _1, timer, strand ) )
);
你会在要点代码中看到我稍微调整了一下,但行为基本上是相同的。)
我在这里期望的是,该股基本上不会做任何事情:我只做过一个处理程序(定时器处理程序)一次在链队列中。因此,我希望计时器能够独立于 PrintNum 调用而计时。但我看到的是 PrintNum 调用仍然具有优先级:所有 5 个调用都必须在 TimerHandler 被允许执行之前完成。
(值得指出的是,Drew Benton 教程中的示例 6c 都是为了确保 TimerHandler 和 PrintNum 不会同时运行。我的变体故意消除了这一保证;我的出发点是想了解示例 6c 存在的问题的解决方案。)
I've been following along with this excellent asio tutorial, but have got confused with exactly what strands do. My understanding was that they worked like a queue for a set of handlers (callbacks) such that the handlers in such a queue would execute in order. But some experiments suggest I am wrong. Can someone explain what they really are?
I'm starting with example 6c It executes PrintNum(1)
to PrintNum(5)
, each with a 1 second delay, before triggering the timer. (Unintuitively, this also happens if I move the PrintNum calls to after starting the timer! Then I realized the request to call TimerHandler does not get on to the strand queue until the timer triggers.)
My first variation was to remove the strand references on just the timer, but leave them on PrintNum (see full code on gist):
strand->post( boost::bind( &PrintNum, 1 ) );
strand->post( boost::bind( &PrintNum, 2 ) );
strand->post( boost::bind( &PrintNum, 3 ) );
strand->post( boost::bind( &PrintNum, 4 ) );
strand->post( boost::bind( &PrintNum, 5 ) );
boost::shared_ptr< boost::asio::deadline_timer > timer(
new boost::asio::deadline_timer( *io_service )
);
timer->expires_from_now( boost::posix_time::seconds( 1 ) );
timer->async_wait( boost::bind( &TimerHandler, _1, timer ) );
Now the timer runs independently of the PrintNum calls. I get the output I expected.
My question comes with my second variation (see gist), where I removed the strand calls for PrintNum, but kept them on the timer:
io_service->post( boost::bind( &PrintNum, 1 ) );
io_service->post( boost::bind( &PrintNum, 2 ) );
io_service->post( boost::bind( &PrintNum, 3 ) );
io_service->post( boost::bind( &PrintNum, 4 ) );
io_service->post( boost::bind( &PrintNum, 5 ) );
boost::shared_ptr< boost::asio::deadline_timer > timer(
new boost::asio::deadline_timer( *io_service )
);
timer->expires_from_now( boost::posix_time::milliseconds( 1000 ) );
timer->async_wait(
strand->wrap( boost::bind( &TimerHandler, _1, timer, strand ) )
);
(You'll see in the gist code that I'm shuffling it up a bit, but the behaviour is basically the same.)
What I expected here is that the strand would basically do nothing: I only ever one handler (TimerHandler) in the strand queue at a time. I therefore expected the timer to tick away independently of the PrintNum calls. But what I see is that the PrintNum calls still get priority: all 5 have to finish before the TimerHandler is allowed to execute.
(It is worth pointing out that example 6c in Drew Benton's tutorial was all about ensuring that neither of TimerHandler and PrintNum would run at the same time. My variations deliberately remove that guarantee; my starting point was wanting to understand the problem that example 6c is the solution to.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我想我现在可以回答自己了。在第二个例子中,问题与strand的使用无关;如果删除了
strand->wrap
,行为(有点)相同。问题是线程都忙!将线程池中的线程数增加到 6,我们得到预期的行为:计时器在创建后 1 秒触发。当线程数量为 5 个或更少时,在第一个PrintNum
调用完成之前,TimerHandler
将不会被调用。正如我在问题中已经指出的那样,理解这个示例的另一件重要的事情是,它是由计时器触发的
strand->wrap()
,而不是TimerHandler< /代码>。当计时器关闭时,
TimerHandler
就会被添加到队列中。但是所有PrintNum
请求都已被调用,并且所有线程都处于繁忙状态!我创建了 另一个要点,将
TimerHandler
放入其自己的 io_service 中,并具有自己的专用线程。现在,无论
PrintNum
调用占用多少线程,计时器都会可靠地运行。 (为了强调这一点,我只为所有 PrintNum 调用提供一个共享线程,强制它们串行运行。)I think I can answer myself now. In the second example, the problem is nothing to do with the use of strand; behaviour is (kind of) identical if the
strand->wrap
is removed. The problem is the threads are all busy! Increase the number of threads in the thread pool to 6 and we get the expected behaviour: the timer triggers 1 second after it is created. When there are 5 or fewer threads, thenTimerHandler
will not get called until the first of thePrintNum
calls finish.The other important thing to understanding this example, as I'd already noted in the question, is that it is
strand->wrap()
that gets triggered by the timer, notTimerHandler
. When the timer goes off it is at that point thatTimerHandler
gets added to the queue. But all thePrintNum
requests have already been called, and all the threads are busy!I created another gist that puts the
TimerHandler
in its own io_service, with its own dedicated thread.Now the timer will run reliably however much of a thread hog the
PrintNum
calls are. (To emphasize that point, I only give one thread for all the PrintNum calls to share, forcing them to run in serial.)