io_service :: poll_one非确定性行为

时间:2016-07-29 18:10:32

标签: c++ boost boost-asio

在下面的代码中,我希望输出始终为1,因为我希望在调用android.app.Fragment时只运行一个处理程序。但是,一次大约300次,输出实际上是3.根据我对boost库的理解,这似乎是不正确的。非确定性行为是错误的还是预期的?

poll_one()

使用boost-asio 1.60.0.6

1 个答案:

答案 0 :(得分:13)

观察到的行为已明确定义且预计会发生,但人们不应期望它经常发生。

Asio有一个有限的链实现池,而strands的默认分配策略是散列。如果发生哈希冲突,则两个链将使用相同的实现。发生哈希冲突时,该示例简化为以下demo

#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()启动堆栈协程 as

    • 如果strand.running_in_this_thread()为调用方返回false,则协程将发布到strand以进行延迟调用
    • 如果strand.running_in_this_thread()为调用方返回true,则会立即执行协程
  • 使用相同实现的离散strand对象仍然保持链的保证。即,不会发生并发执行,并且order of handler invocation定义良好。当离散的strand对象使用离散实现,并且多个线程正在运行io_service时,可以观察到并发的并发链。但是,当离散的strand对象使用相同的实现时,即使多个线程正在运行io_service,也不会观察到并发性。此行为是documented

      

    该实现不保证将同时调用通过不同strand对象发布或分派的处理程序。

  • Asio有一个有限的链实现池。当前默认值为193,可以通过将BOOST_ASIO_STRAND_IMPLEMENTATIONS定义为所需的数字来控制。 Boost.Asio 1.48 release notes

    中注明了此功能
      

    通过将BOOST_ASIO_STRAND_IMPLEMENTATIONS定义为所需的数字,可以配置链实现的数量。

    通过减小池大小,可以增加两个离散链将使用相同实现的机会。使用原始代码,如果要将池大小设置为1,则strand1strand2将始终使用相同的实现,从而导致val始终为{{ 1}}(demo)。

  • 分配链实现的默认策略是使用黄金比例哈希。由于使用了散列算法,因此存在冲突的可能性,导致相同的实现被用于多个离散的3对象。通过定义strand,可以将分配策略更改为循环,防止在BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION链分配发生之前发生冲突。 Boost.Asio 1.48发行说明中注明了此功能:

      

    添加了对新BOOST_ASIO_STRAND_IMPLEMENTATIONS + 1标志的支持,该标志将链实现的分配切换为使用循环方法而不是散列。

鉴于上述细节,在原始代码中观察到BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION时会发生以下情况:

  • 1strand1具有离散的实现
  • 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)时:

  • 3strand1发生哈希冲突,导致它们使用相同的基础链实现
  • strand2执行直接发布到io_service::poll_one()
  • 的单个处理程序
  • 发布到strand1的处理程序将strand1设置为val
  • 发布到1的处理程序已入队并且从未被调用
  • 会立即在strand2内创建并调用协程,将boost::asio::spawn()设置为val,因为3可以安全地执行协程,同时保证非并发执行的保证和处理程序调用的顺序