io_service::poll_one 非确定性行为

2024-04-17

在下面的代码中,我希望输出始终为 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;
}

使用boost-asio 1.60.0.6


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

Asio 的链实现池有限,链的默认分配策略是哈希。如果发生哈希冲突,两个链将使用相同的实现。当发生哈希冲突时,示例简化为以下内容demo http://coliru.stacked-crooked.com/a/82c283c7a8388d52:

#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() http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service__strand/dispatch.html, as strand2.dispatch()从处理程序中调用,其中strand2.running_in_this_thread()回报true

观察到的行为有多种细节:

  • io_service::poll_one() http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service/poll_one/overload1.html将运行io_service的事件循环并且没有阻塞,它将最多执行一个准备运行的处理程序。处理程序在上下文中立即执行dispatch()永远不会排队到io_service,并且不受poll_one()调用单个处理程序的限制。

  • The boost::asio::spawn(strand, function) http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/spawn/overload3.html过载启动一个堆栈协程as-if by strand.dispatch():

    • if strand.running_in_this_thread()回报false对于调用者,那么协程将被发布到strand用于延迟调用
    • if strand.running_in_this_thread()回报true对于调用者,那么协程将立即执行
  • 离散的strand使用相同实现的对象仍然保持链的保证。即,不会发生并发执行,并且处理程序调用顺序 http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service__strand.html#boost_asio.reference.io_service__strand.order_of_handler_invocation是明确定义的。当离散时strand对象使用离散实现,并且多个线程正在运行io_service,那么我们可以观察到离散的线程同时执行。然而,当离散strand对象使用相同的实现,即使多个线程正在运行,也不会观察到并发性io_service。这种行为是有记录的 http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service__strand.html#boost_asio.reference.io_service__strand.remarks:

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

  • Asio 的链实现池有限。当前默认值是193并且可以通过定义来控制BOOST_ASIO_STRAND_IMPLEMENTATIONS到所需的数量。此功能在Boost.Asio 1.48 发行说明 http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/history.html#boost_asio.history.asio_1_6_1___boost_1_48

    通过定义可配置链实现的数量BOOST_ASIO_STRAND_IMPLEMENTATIONS到所需的数量。

    通过减小池大小,可以增加两个离散链使用相同实现的机会。使用原始代码,如果将池大小设置为1, then strand1 and strand2将始终使用相同的实现,从而导致val总是存在3 (demo http://coliru.stacked-crooked.com/a/d98ac182372d4192).

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

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

鉴于上述详细信息,当以下情况发生时1在原始代码中观察到:

  • strand1 and strand2有离散的实现
  • io_service::poll_one()执行直接发布到的单个处理程序strand1
  • 发布到的处理程序strand1 sets val to 1
  • 处理程序发布到strand2已排队且从未被调用
  • 协程的创建被推迟,因为strand的调用顺序保证可以防止协程被创建,直到上一个处理程序被发布到之后strand2已执行:

    给定一个链对象s, if s.post(a)发生在之前s.dispatch(b),后者在链外执行,然后asio_handler_invoke(a1, &a1)发生在之前asio_handler_invoke(b1, &b1).

另一方面,当3观察到:

  • 发生哈希冲突strand1 and strand2,导致它们使用相同的底层链实现
  • io_service::poll_one()执行直接发布到的单个处理程序strand1
  • 发布到的处理程序strand1 sets val to 1
  • 处理程序发布到strand2已排队且从未被调用
  • 协程立即被创建并被调用boost::asio::spawn(), 环境val to 3, as strand2可以安全地执行协程,同时保持非并发执行的保证和处理程序调用的顺序
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

io_service::poll_one 非确定性行为 的相关文章

  • 结构化绑定中缺少类型信息

    我刚刚了解了 C 中的结构化绑定 但有一件事我不喜欢 auto x y some func is that auto正在隐藏类型x and y 我得抬头看看some func的声明来了解类型x and y 或者 我可以写 T1 x T2 y
  • BASIC 中的 C 语言中的 PeekInt、PokeInt、Peek、Poke 等效项

    我想知道该命令的等效项是什么Peek and Poke 基本和其他变体 用 C 语言 类似PeekInt PokeInt 整数 涉及内存条的东西 我知道在 C 语言中有很多方法可以做到这一点 我正在尝试将基本程序移植到 C 语言 这只是使用
  • 根据属性的类型使用文本框或复选框

    如果我有这样的结构 public class Parent public string Name get set public List
  • 在一个数据访问层中处理多个连接字符串

    我有一个有趣的困境 我目前有一个数据访问层 它必须与多个域一起使用 并且每个域都有多个数据库存储库 具体取决于所调用的存储过程 目前 我只需使用 SWITCH 语句来确定应用程序正在运行的计算机 并从 Web config 返回适当的连接字
  • 类型中的属性名称必须是唯一的

    我正在使用 Entity Framework 5 并且有以下实体 public class User public Int32 Id get set public String Username get set public virtual
  • 机器Epsilon精度差异

    我正在尝试计算 C 中双精度数和浮点数的机器 epsilon 值 作为学校作业的一部分 我在 Windows 7 64 位中使用 Cygwin 代码如下 include
  • 传递给函数时多维数组的指针类型是什么? [复制]

    这个问题在这里已经有答案了 我在大学课堂上学习了 C 语言和指针 除了多维数组和指针之间的相似性之外 我认为我已经很好地掌握了这个概念 我认为由于所有数组 甚至多维 都存储在连续内存中 因此您可以安全地将其转换为int 假设给定的数组是in
  • 如何在 C++ 中标记字符串?

    Java有一个方便的分割方法 String str The quick brown fox String results str split 在 C 中是否有一种简单的方法可以做到这一点 The 增强分词器 http www boost o
  • 无限循环与无限递归。两者都是未定义的吗?

    无副作用的无限循环是未定义的行为 看here https coliru stacked crooked com view id 24e0a58778f67cd4举个例子参考参数 https en cppreference com w cpp
  • C++ 多行字符串原始文字[重复]

    这个问题在这里已经有答案了 我们可以像这样定义一个多行字符串 const char text1 part 1 part 2 part 3 part 4 const char text2 part 1 part 2 part 3 part 4
  • WPF 数据绑定到复合类模式?

    我是第一次尝试 WPF 并且正在努力解决如何将控件绑定到使用其他对象的组合构建的类 例如 如果我有一个由两个单独的类组成的类 Comp 为了清楚起见 请注意省略的各种元素 class One int first int second cla
  • 两个静态变量同名(两个不同的文件),并在任何其他文件中 extern 其中一个

    在一个文件中将变量声明为 static 并在另一个文件中进行 extern 声明 我认为这会在链接时出现错误 因为 extern 变量不会在任何对象中看到 因为在其他文件中声明的变量带有限定符 static 但不知何故 链接器 瑞萨 没有显
  • 在 WPF 中使用 ReactiveUI 提供长时间运行命令反馈的正确方法

    我有一个 C WPF NET 4 5 应用程序 用户将用它来打开某些文件 然后 应用程序将经历很多动作 读取文件 通过许多插件和解析器传递它 这些文件可能相当大 gt 100MB 因此这可能需要一段时间 我想让用户了解 UI 中发生的情况
  • C# 中最小化字符串长度

    我想减少字符串的长度 喜欢 这串 string foo Lorem ipsum dolor sit amet consectetur adipiscing elit Aenean in vehicula nulla Phasellus li
  • 为什么 std::uint32_t 与 uint32_t 不同?

    我对 C 有点陌生 我有一个编码作业 很多文件已经完成 但我注意到 VS2012 似乎有以下语句的问题 typedef std uint32 t identifier 不过 似乎将其更改为 typedef uint32 t identifi
  • C++ 中的参考文献

    我偶尔会在 StackOverflow 上看到代码 询问一些涉及函数的重载歧义 例如 void foo int param 我的问题是 为什么会出现这种情况 或者更确切地说 你什么时候会有 对参考的参考 这与普通的旧参考有何不同 我从未在现
  • DotNetZip:如何提取文件,但忽略zip文件中的路径?

    尝试将文件提取到给定文件夹 忽略 zip 文件中的路径 但似乎没有办法 考虑到其中实现的所有其他好东西 这似乎是一个相当基本的要求 我缺少什么 代码是 using Ionic Zip ZipFile zf Ionic Zip ZipFile
  • 在OpenGL中,我可以在坐标(5, 5)处精确地绘制一个像素吗?

    我所说的 5 5 正是指第五行第五列 我发现使用屏幕坐标来绘制东西非常困难 OpenGL 中的所有坐标都是相对的 通常范围从 1 0 到 1 0 为什么阻止程序员使用屏幕坐标 窗口坐标如此严重 最简单的方法可能是通过以下方式设置投影以匹配渲
  • MySQL Connector C/C API - 使用特殊字符进行查询

    我是一个 C 程序 我有一个接受域名参数的函数 void db domains query char name 使用 mysql query 我测试数据库中是否存在域名 如果不是这种情况 我插入新域名 char query 400 spri
  • 现代编译器是否优化乘以 1 和 -1

    如果我写 template

随机推荐