asio/strand:为什么计时器的行为不同

asio/strand: why is behaviour with timer different?

本文关键字:计时器 strand 为什么 asio      更新时间:2023-10-16

我一直在学习这篇优秀的asio教程,但对链的确切作用感到困惑。我的理解是,它们就像一组处理程序(回调)的队列一样工作,这样队列中的处理程序就会按顺序执行。但一些实验表明我错了。有人能解释一下它们到底是什么吗?

我从示例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 ) )
);

(你会在要点代码中看到,我把它打乱了一点,但行为基本上是一样的。)

我在这里所期望的是,strand基本上什么都不做:我一次在strand队列中只有一个处理程序(TimerHandler)。因此,我期望计时器独立于PrintNum调用而滴答作响。但我看到的是,PrintNum调用仍然具有优先级:在TimerHandler被允许执行之前,所有5个调用都必须完成。

(值得指出的是,Drew Benton教程中的示例6c完全是为了确保TimerHandler和PrintNum都不会同时运行。我的变体故意取消了这种保证;我的出发点是想了解示例6c是解决问题的方法。)

我想我现在可以自己回答了。在第二个例子中,问题与strand的使用无关;如果去除CCD_ 3,则行为(有点)相同。问题是线程都很忙将线程池中的线程数增加到6,我们就会得到预期的行为:计时器在创建后1秒触发。当有5个或更少的线程时,TimerHandler将不会被调用,直到第一个PrintNum调用完成。

理解这个例子的另一件重要的事情,正如我在问题中已经指出的,是strand->wrap()被计时器触发,而不是TimerHandler。当计时器关闭时,TimerHandler将被添加到队列中。但是所有的PrintNum请求都已经被调用了,所有的线程都很忙!

我创建了另一个要点,它将TimerHandler放在自己的io_service中,并带有自己的专用线程。

boost::shared_ptr< boost::asio::io_service > io_service2(
        new boost::asio::io_service
);
boost::shared_ptr< boost::asio::io_service::work > work2(
        new boost::asio::io_service::work( *io_service2 )
);
boost::shared_ptr< boost::asio::io_service::strand > strand2(
        new boost::asio::io_service::strand( *io_service2 )
);
...
boost::thread_group worker_threads;
for( int x = 0; x < 2; ++x )
{
        worker_threads.create_thread( boost::bind( &WorkerThread, x==0? io_service2 : io_service ) );
}
...
boost::shared_ptr< boost::asio::deadline_timer > timer(
        new boost::asio::deadline_timer( *io_service2 )
);
timer->expires_from_now( boost::posix_time::milliseconds( 1000 ) );
timer->async_wait(
        strand2->wrap( boost::bind( &TimerHandler, _1, timer, strand2 ) )
);

现在,无论PrintNum调用占用了多少线程,计时器都将可靠地运行。(为了强调这一点,我只为所有PrintNum调用提供一个线程来共享,迫使它们串行运行。)