使用Main / Workers线程进行Boost :: Asio - 我可以在发布工作之前启动事件循环吗?

时间:2015-05-07 20:11:41

标签: c++ multithreading boost boost-asio signals-slots

我是Boost :: Asio的新手。我想要一个"经理"处理工作线程上的无锁队列,并将结果发送回主线程。从这里的答案(Boost Asio pattern with GUI and worker thread)中大量借用我已经能够接近我想要的东西了。

以下是代码:

#include "stdafx.h"

#include <boost/asio.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <boost/signals2.hpp>

#include "Task.h"


class Manager
{
    typedef boost::signals2::signal<void(int)> Signal;

public:
    Manager()
    {
      Signal signal;
      std::cout << "Main thread: " << std::this_thread::get_id() << std::endl;

      _mainWork = boost::in_place(boost::ref(_mainService));
      _workerWork = boost::in_place(boost::ref(_workerService));

      _workerThread = std::thread(&Manager::workerMain, this);
      _workerService.post(std::bind(&Manager::ProcessData, this, boost::ref(signal)));
   };

   virtual ~Manager() {};

   void workerMain()
   {
      std::cout << "Worker thread: " << std::this_thread::get_id() << std::endl;
      _workerService.poll();
   }

   void processResult(unsigned int x)
   {
      int result = x - 1;
      std::cout << "Processing result = " << result << " on thread " << std::this_thread::get_id() << std::endl;

      _numItemsPulled++;
      if (_numItemsPushed == _numItemsPulled)
      {
         _mainWork = boost::none;
         _mainService.stop();
      }
   }

   void ProcessData(Signal& signal)
   {
      bool shutdown = false;

      do
      {
         queue.consume_one([&](std::shared_ptr<Task> task)
         {
            if (task->IsShutdownRequest())
            {
               shutdown = true;
               std::cout << "Shutting down on thread " << std::this_thread::get_id() << std::endl;
            }

            if (shutdown == false)
            {
               std::cout << "Getting element from queue on thread " << std::this_thread::get_id() << std::endl;
               int result = task->Execute();
               _mainService.post(std::bind(&Manager::processResult, this, result));
            }
         });

      } while (shutdown == false);
   }

   void Push(int x)
   {
      if (x > 0)
      {
         std::shared_ptr<TimeSeriesTask> task = std::make_shared<TimeSeriesTask>(x);
         queue.push(task);
         _numItemsPushed++;
      }
      else
      {
         std::shared_ptr<ShutdownTask> task = std::make_shared<ShutdownTask>();
         queue.push(task);
      }
   }

   void QueueData(int x)
   {
      Push(x);
   }

   void StartEventLoop()
   {
      while (_mainService.stopped() == false)
      {
         _mainService.poll();
      }
   }

   void Cleanup()
   {
      _workerWork = boost::none;
      _workerThread.join();
   }

private:
   boost::asio::io_service _mainService;
   boost::optional<boost::asio::io_service::work> _mainWork;
   boost::asio::io_service _workerService;
   boost::optional<boost::asio::io_service::work> _workerWork;
   std::thread _workerThread;

   int _numItemsPushed = 0;
   int _numItemsPulled = 0;
   boost::lockfree::spsc_queue<std::shared_ptr<Task>, boost::lockfree::capacity<1024>> queue;
};


int main()
{
   std::shared_ptr<Manager> mgr = std::make_shared<Manager>();

   mgr->QueueData(1);
   mgr->QueueData(2);
   mgr->QueueData(3);

   mgr->StartEventLoop(); //why does work need to be posted first?

   mgr->QueueData(-1);
   mgr->Cleanup();

   return 0;
}

正如我的评论行所示,是否有办法在发布工作/排队数据之前启动事件循环?目标是让事件循环始终轮询并让其他对象按需排队数据。我试图在Manager的构造函数中启动它,但如果后来发布了工作,则不会处理任何工作。

另外一个注意事项:我无法使用run来阻止,因此轮询似乎是正确的选择。

非常感谢任何关于我失踪的指导。感谢。

其他信息:我尝试在StartEventLoop()中删除while循环,并在排队数据之前调用它。数据在工作线程上排队并计算,但结果永远不会被发送回主线程。

1 个答案:

答案 0 :(得分:1)

  1. 这是一个问题

    Signal signal;
    // ...
    _workerService.post(std::bind(&Manager::ProcessData, this, boost::ref(signal)));
    

    您将任务绑定到之后立即停止的本地变量。您的计划会调用Undefined Behaviour

  2. 这也是

    void workerMain()
    {
       std::cout << "Worker thread: " << std::this_thread::get_id() << std::endl;
       _workerService.poll();
    }
    

    poll将返回,因此线程会过早退出。使用run()保留线程,直到重置_workerWork

  3. 最大问题 是您将无限处理循环发布到事件队列中。 ProcessData不会返回,这就是阻止队列的原因(并且只有一个服务线程,所以它是一个永久阻止)。

    如果你改变它摆脱循环,但只是在完成后重新发布:

    void ProcessData(Signal &signal) {
        bool shutdown = false;
    
        queue.consume_one([&](std::shared_ptr<Task> task) {
            if (task->IsShutdownRequest()) {
                shutdown = true;
                std::cout << "Shutting down on thread " << std::this_thread::get_id() << std::endl;
            }
    
            if (shutdown == false) {
                std::cout << "Getting element from queue on thread " << std::this_thread::get_id() << std::endl;
                int result = task->Execute();
                _mainService.post(std::bind(&Manager::processResult, this, result));
            }
        });
    
        if (!shutdown)
            _workerService.post(std::bind(&Manager::ProcessData, this, boost::ref(signal)));
    }
    

    这至少不会死锁,但可能会产生非常高的CPU负载。

  4. 最后,您需要异步StartEventLoop,否则它将无法返回。让它在一个单独的线程上运行似乎是一个选项,但很可能是违反spsc_queue的线程模型要求的方法...请注意这里。

  5. 坦率地说,所有这些看起来都无可救药地过于复杂。我怀疑你

    • 要么 需要低延迟的高吞吐量处理,在这种情况下你根本不需要asio,只需在{{1上进行一次workerThread循环}}

      <强> Live Demo

      见下面的列表

    • 您希望在隔离线程上进行低成本排队和处理。在这种情况下,使用一个老式的锁定队列,例如

    演示列表

    consume_one