2016-07-29 73 views
13

在下面的代码中,我期望输出始终为1,因为我期望调用poll_one()时只有一个处理程序运行。然而,一次约300次,输出实际上是3.根据我对boost库的理解,这看起来不正确。非确定性行为是错误还是预期?io_service :: poll_one非确定性行为

#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; 
} 

使用升压ASIO 1.60.0.6

+0

为什么要downvote?回答赞赏 –

+0

当然,它是完整的最小和可验证,并易于编译。异常不会抛出。 –

+0

如果您将案例数量从3个减少到2个,问题不会出现 –

回答

12

所观察到的行为是明确界定,并预期会发生,但我们不应该期望它经常发生。

Asio有一个有限的链实现池,默认的分配策略是哈希。如果发生散列冲突,则两条链将使用相同的实现。当散列碰撞发生时,例如简化为以下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永远不会调用
  • handler3strand2.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)超载启动stackful协程AS-如果strand.dispatch()

    • 如果strand.running_in_this_thread()回报false为调用者,那么协同程序将被张贴到strand延期调用
    • 如果strand.running_in_this_thread()返回true为调用者,那么协程将立即执行
  • 分立strand使用相同实现的对象仍然保持链的保证。即,并发执行不会发生,并且定义良好。当离散的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始终为3demo)。

  • 分配链实现的默认策略是使用黄金比例散列。使用散列算法时,可能会发生冲突,导致多个分立的对象使用相同的实现。通过定义BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION,可以将分配策略更改为循环,从而防止发生冲突,直到发生分支分配为止。此功能在Boost.Asio的1.48版本说明注释:

    BOOST_ASIO_ENABLE_SEQUENTIAL_STRAND_ALLOCATION标志,该标志开关链实现的分配使用循环的方法,而不是散列增加的支持。

    • strand1strand2具有离散实现
    • io_service::poll_one()执行,其直接贴在单一处理程序:

鉴于上述细节,当1在原始代码中观察到发生以下进入strand1

  • 处理程序被张贴到strand1val1
  • 处理程序发布到strand2入列,亦从未援引
  • 的协同程序的创建被延迟,为strand的顺序调用保证防止协程从已发布的上一处理程序后,正在创建直到成strand2已执行:

    给予链对象s,如果s.post(a)之前发生s.dispatch(b),其中后者的链的外部执行,然后asio_handler_invoke(a1, &a1)之前发生asio_handler_invoke(b1, &b1)

  • 在另一方面,当3观察:

    • 散列碰撞发生时用于strand1strand2,导致它们使用相同的基本链实施
    • io_service::poll_one()执行单个处理程序直接发布到strand1
    • 发布到012的处理程序套val1
    • 处理程序发布到strand2入列,亦从未援引
    • 协程立即创建并在boost::asio::spawn()调用,设置val3,作为strand2可以安全地执行,同时保持的非保障的协同程序并发执行和处理程序调用的顺序
    +4

    这个答案是一件艺术品。我喜欢边缘情况下的小型演示。 +100 AFAIAC – sehe

    +0

    真棒回答。这对于链类的文档是非常有价值的信息。 – hifier

    +0

    @sehe谢谢!多年来,您一直致力于提供优秀的答案,这对我来说是一个巨大的动力。没有你,这个答案就不会存在。 –