提升ASIO IO_SERVICE实施?

时间:2012-03-20 03:01:19

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

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

我写了一个简单的小基准来测量性能。基准测试只是记录一百万条消息的单个线程(将其推入缓冲区),我的工作线程只是从队列中获取消息以记录到文件/控制台/记录器列表。 (P.S.互斥和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 asio那样执行/扩展。

  4. 结果:(我只有1个工人线程)

    • 1000任务:两种情况下10微秒/任务
    • 10000任务:80微秒(有界缓冲区),10微秒增强asio
    • 100000任务:250微秒(bounde缓冲区),10秒微秒增强asio

    知道boost如何解决处理程序的io_service线程安全队列中的线程安全问题会很有趣(我总是认为在某个实现级别它们也必须使用锁和c.v)。

3 个答案:

答案 0 :(得分:4)

我担心我对(1)无能为力,但对于其他两个问题:

(2)我发现boost::asio架构中存在一些非确定性的开销,即进入(或发送到IO服务对象)的数据之间的延迟可能与虚拟实际不同瞬间响应高达数百毫秒的数量级。我试图量化这个作为我试图解决的另一个问题的一部分,关于日志和时间戳RS232数据,但没有得到任何结论性的结果或方法来稳定延迟。我不会惊讶地发现上下文切换组件存在类似的问题。

(3)就异步I / O以外的任务使用boost::asio而言,它现在是我大多数异步操作的标准工具。我一直使用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的设备类中,您可以进一步包装线程池管理内容。我总是试图根据时间来编写一个智能池自动管理器来适当增长线程池,但我还没有一个必要的项目。

关于满足您对线程安全的好奇心,有可能深入了解提升的内容,以确切了解他们如何做他们正在做的事情。就个人而言,我总是把大部分提升的东西都用在面值上,并且根据过去的经验假设它在引擎盖下非常优化。

答案 1 :(得分:0)

我还发现boost::asio是一般多核处理引擎的优秀基础设施。我通过大量同步来测量它在细粒度任务上的性能,并发现它优于我使用C ++ 11线程和条件变量编写的“经典”实现。

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

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

  1. 优先级队列和
  2. 工作窃取队列。
  3. 我怀疑添加这些功能会降低TBB的性能水平。

答案 2 :(得分:0)

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

以下内容摘自boost文档。

  

线程安全性通常,同时使用以下内容是安全的   不同的对象,但同时使用单个对象并不安全   宾语。但是,诸如io_service之类的类型提供了更强大的保证   同时使用单个对象很安全

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

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

  

Linux Kernel 2.6多路分解机制:•将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