【问题标题】:Boost ASIO IO_SERVICE Implementation?提升 ASIO IO_SERVICE 实施?
【发布时间】:2012-04-04 13:36:15
【问题描述】:

我正在编写一个异步日志框架,其中有多个线程转储数据。我开始使用 Boost asio,因为它提供了一些简单的方法来强制执行序列化和排序。由于我是初学者,因此我从线程安全(使用 boost::mutexboost:condition_variable)循环 bounded_buffer(实际上是向量)开始我的设计。

我写了一个简单的小基准来衡量性能。基准测试只是一个记录一百万条消息的单个线程(将其推入缓冲区),我的工作线程只会从队列中获取消息以记录到文件/控制台/记录器列表。 (P.S. mutex 和 C.V 的使用是正确的,并且指向消息的指针正在移动,所以从这个角度来看,一切都很好/高效)。

当我将实现更改为使用 boost::asio::io_service 并让单个线程执行 run() 时,性能确实得到了提升(实际上,它在增加记录的消息数量而不是在我最初的性能下降方面做得很好简单模型)

这里有几个我想澄清的问题。

  1. 为什么要提高性能? (我认为boost::asio::io_service 内部实现有处理程序的线程安全队列,是什么让它比我自己最初的简单线程安全队列设计更有效)。请注意,我的设计经过了很好的审查并且没有任何错误(骨架代码基于经过验证的示例),有人可以更详细地了解io_service 如何实现这一点的内部细节。

  2. 第二个有趣的观察结果是,在增加线程时,我的初始实现性能有所提高,但以丢失序列化/排序为代价,但使用 boost::asio 时性能下降(非常轻微)(我认为这是因为我的处理程序正在执行非常简单的任务并且上下文切换开销正在下降,我将尝试放置更复杂的任务并稍后发布我的观察结果。

  3. 我真的很想知道boost::asio 是否仅用于 i/o 和网络操作,或者我使用它通过线程池执行并发任务(并行)是一种很好的设计方法。 io_service 对象是否只是用于 i/o 对象(如文档中所写),但我发现它是一种非常有趣的方式,可以帮助我以序列化方式解决并发任务(不仅仅是 i/o 或网络相关)(有时使用链强制排序)。我是 boost 新手,真的很好奇为什么基本模型的性能/规模不如我使用 boost asio 时那么好。

结果:(在这两个中我只有 1 个工作线程)

  • 1000 个任务:两种情况下每个任务都需要 10 微秒
  • 10000 个任务:80 微秒(有界缓冲区),在 boost asio 中为 10 微秒
  • 100000 任务:250 微秒(边界缓冲区),10 微秒 in boost asio

了解 boost 如何解决处理程序的 io_service 线程安全队列中的线程安全问题会很有趣(我一直认为在某种实现级别上,他们也必须使用锁和 c.v )。

【问题讨论】:

  • 我使用boost::asio 来管理其他类型的异步任务(不仅仅是网络 IO)并取得了很好的成功。
  • 我知道这是一个老问题,但可能是(在 Windows 上),ASIO 使用 IO 完成端口,这可能会导致整体系统调用减少。

标签: multithreading boost thread-safety boost-asio threadpool


【解决方案1】:

恐怕我对 (1) 帮不上什么忙,但关于其他两个问题:

(2) 我发现boost::asio 架构中存在一些不确定性的开销,即数据传入(或发送到 IO 服务对象)之间的延迟可能与几乎即时响应不同高达数百毫秒的数量级。我试图将其量化为我试图解决的关于记录和时间戳 RS232 数据的另一个问题的一部分,但没有得到任何确凿的结果或稳定延迟的方法。如果发现上下文切换组件存在类似问题,我一点也不感到惊讶。

(3) 至于将boost::asio 用于异步 I/O 以外的任务,它现在是我用于大多数异步操作的标准工具。我一直使用boost::asio 定时器来处理异步进程,并为其他任务生成超时。将多个工作线程添加到池中的能力意味着您也可以为其他异步高负载任务很好地扩展解决方案。去年我写的最简单和最喜欢的课程是用于boost::asio IO 服务的一个很小的工作线程类(如果有任何拼写错误,请原谅,这是记忆中的转录,而不是剪切和粘贴):

class AsioWorker
{
public:
  AsioWorker(boost::asio::io_service * service):
  m_ioService(service), m_terminate(false), m_serviceThread(NULL)
  {
    m_serviceThread = new boost::thread( boost::bind( &AsioWorker::Run, this ) )
  }
  void Run( void )
  {
    while(!m_terminate)
      m_ioService->poll_one();
      mySleep(5); // My own macro for cross-platform millisecond sleep
  }
  ~AsioWorker( void )
  {
    m_terminate = true;
    m_serviceThread->join();
  }
private:
  bool m_terminate;
  boost::asio::io_service *m_ioService;
  boost::thread *m_serviceThread;
}

这个类是一个很棒的小玩具,只需根据需要添加 new ,并在完成后添加 delete 一些。将std::vector<AsioWorker*> m_workerPool 粘贴到使用boost::asio 的设备类中,您可以进一步包装线程池管理内容。我一直很想根据时间编写一个智能池自动管理器来适当地增加线程池,但我还没有一个需要它的项目。

为了满足您对线程安全的好奇心,可以深入挖掘 boost 的内脏,以准确了解它们是如何做他们正在做的事情的。就我个人而言,我总是从表面上看待大部分提升的东西,并根据过去的经验假设它在引擎盖下得到了很好的优化。

【讨论】:

    【解决方案2】:

    我还发现boost::asio 是通用多核处理引擎的出色基础架构。我测量了它在具有大量同步的细粒度任务上的性能,发现它优于我使用 C++11 线程和条件变量编写的“经典”实现。

    它的表现也优于 TBB,但没有那么多。我深入研究了他们的代码,试图找到“秘密”。我能看到的唯一想法是他们的队列是一个经典的链表,而不是一个 stl 容器。

    尽管如此,我不确定asio 在 Xeon Phi 这样的大规模线程架构上的扩展能力如何。似乎缺少的两件事是:

    1. 一个优先队列和
    2. 工作窃取队列。

    我怀疑添加这些功能会将其降低到 TBB 性能水平。

    【讨论】:

      【解决方案3】:

      @user179156。非常有趣的问题。我还想知道您帖子中 (1) 下列出的类似问题。这是我发现的

      以下内容摘自 boost 文档。

      线程安全一般来说,并发使用是安全的 不同的对象,但同时使用单个对象是不安全的 目的。但是 io_service 等类型提供了更强的保证 同时使用单个对象是安全的

      问题是 Boost ASIO 为何如此受欢迎,以及如何在内部实施以确保效率。

      这里是文档的摘录,可以深入了解它是如何在内部实现的。

      Linux Kernel 2.6 Demultiplexing 机制: • 使用 epoll 解复用。线程: • 使用 epoll 进行多路分离是在 调用 io_service::run() 的线程之一, io_service::run_one()、io_service::poll() 或 io_service::poll_one()。 • 每个 io_service 的附加线程用于模拟异步 主机分辨率

      现在让我们看看如何让 boost 的内部线程和解复用器线程之间的通信更高效。如果我要从头开始编写 boost asio。这是我的做法

      在管道中使用 epoll

      因此,为了在线程之间传递消息,我将简单地传递指向我在 boost::post 中传递的对象的指针。 这是一个有趣的link,关于如何有效地在线程之间传递消息

      【讨论】:

        猜你喜欢
        • 2020-05-02
        • 1970-01-01
        • 2011-01-20
        • 2018-06-13
        • 1970-01-01
        • 2017-09-18
        • 2017-09-17
        • 1970-01-01
        • 2011-06-16
        相关资源
        最近更新 更多