如何将信号/数据从工作线程发送到主线程?

时间:2015-05-09 00:26:56

标签: multithreading c++11 boost boost-asio boost-signals2

我先说这是我第一次钻研多线程。尽管对并发和同步进行了大量阅读,但我并没有轻易找到满足我的要求的解决方案。

使用C ++ 11和Boost,我试图弄清楚如何将数据从工作线程发送到主线程。工作线程在应用程序启动时生成,并持续监视无锁队列。对象以不同的间隔填充此队列。这部分正在运作。

一旦数据可用,它就需要由主线程处理,因为另一个信号将被发送到不能在工作线程上的应用程序的其余部分。这就是我的意思我遇到了麻烦。

如果我必须通过互斥锁或条件变量阻塞主线程,直到工作线程完成,那将如何提高响应能力?我不妨留下一个线程,所以我可以访问数据。我必须在这里遗漏一些东西。

我发了几个问题,认为Boost :: Asio是要走的路。有一个例子说明如何在线程之间发送信号和数据,但正如响应所示,事情变得过于复杂并且不能完美地运行:

How to connect signal to boost::asio::io_service when posting work on different thread?

Boost::Asio with Main/Workers threads - Can I start event loop before posting work?

与一些同事交谈后,建议使用两个队列 - 一个输入,一个输出。这将在共享空间中,输出队列将由工作线程填充。工作线程总是在运行但是需要一个Timer,可能是在应用程序级别,它会强制主线程检查输出队列以查看是否有任何挂起的任务。

关于我应该引起注意的任何想法?是否有任何技术或策略可以用于我正在尝试做的事情?我接下来会看着计时器。

感谢。

编辑:这是对后处理模拟结果的插件系统的生产代码。我们首先使用C ++ 11,然后是Boost。我们正在使用Boost的lockfree :: queue。应用程序在单个线程上执行我们想要的操作,但现在我们正在尝试优化我们看到存在性能问题的位置(在这种情况下,通过另一个库进行计算)。主线程有很多责任,包括数据库访问,这就是我想限制工作线程实际做什么的原因。

更新:我已经成功使用std :: thread来启动一个工作线程来检查一个Boost lock :: free队列并处理放入它的任务。这是@Pressacco的第5步回答我遇到了麻烦。任何一个示例在工作线程完成时将值返回给主线程并通知主线程,而不是简单地等待工作者完成?

2 个答案:

答案 0 :(得分:1)

如果你的目标是从头开始开发解决方案(使用本机线程,队列等):

  1. 创建一个线程保存队列队列(Mutex / CriticalSection周围添加/删除)
  2. 创建与队列
  3. 关联的计数信号量
  4. 有一个或多个工作线程等待计数信号量(即线程将阻塞)
    • 信号量比线程不断轮询队列
    • 更有效
  5. 将消息/作业添加到队列中,增加信号量
    • 线程将被唤醒
    • 线程应删除一条消息
  6. 如果需要返回结果......
    • 设置另一个:Queue + Semaphore + WorkerThread s
  7. 其他说明

    如果您决定从头开始实现线程安全队列,请查看:

    话虽如此,我还会再看看BOOST。我还没有使用过库,但据我所知,它很可能包含一些相关的数据结构(例如线程安全队列)。

    我最喜欢的引用来自MSDN

      

    "当您使用任何类型的多线程时,您可能会暴露   你自己对非常严重和复杂的错误"

    <强> SIDEBAR

    由于您是第一次查看并发编程,因此您可能需要考虑:

    • 你的目标是建立有价值的生产代码,还是仅仅是一个学习练习?
      • 生产?考虑我们现有的成熟图书馆
      • 学习?考虑从头开始编写代码
    • 考虑使用带有异步回调的线程池而不是本机线程。
    • 更多主题!=更好
    • 真的需要线程吗?
    • 关注KISS principle

答案 1 :(得分:0)

上面的反馈使我朝着正确的方向前进,为我所需要的。该解决方案绝对比我之前尝试使用信号/插槽或Boost :: Asio更简单。我有两个无锁队列,一个用于输入(在工作线程上)和一个用于输出(在主线程上,由工作线程填充)。我使用计时器来安排何时处理输出队列。代码如下;也许这对某人有用:

//Task.h

#include <iostream>
#include <thread>


class Task
{
public:
   Task(bool shutdown = false) : _shutdown(shutdown) {};
   virtual ~Task() {};

   bool IsShutdownRequest() { return _shutdown; }

   virtual int Execute() = 0;

private:
   bool _shutdown;
};


class ShutdownTask : public Task
{
public:
   ShutdownTask() : Task(true) {}

   virtual int Execute() { return -1; }
};


class TimeSeriesTask : public Task
{
public:
   TimeSeriesTask(int value) : _value(value) {};

   virtual int Execute()
   {
      std::cout << "Calculating on thread " << std::this_thread::get_id() << std::endl;
      return _value * 2;
   }

private:
   int _value;
};


// Main.cpp : Defines the entry point for the console application.

#include "stdafx.h"
#include "afxwin.h"

#include <boost/lockfree/spsc_queue.hpp>

#include "Task.h"

static UINT_PTR ProcessDataCheckTimerID = 0;
static const int ProcessDataCheckPeriodInMilliseconds = 100;


class Manager
{
public:
   Manager() 
   {
      //Worker Thread with application lifetime that processes a lock free queue
      _workerThread = std::thread(&Manager::ProcessInputData, this);
   };

   virtual ~Manager() 
   {
      _workerThread.join();
   };

   void QueueData(int x)
   {
      if (x > 0)
      {
         _inputQueue.push(std::make_shared<TimeSeriesTask>(x));
      }
      else
      {
         _inputQueue.push(std::make_shared<ShutdownTask>());
      }
   }

   void ProcessOutputData()
   {
      //process output data on the Main Thread
      _outputQueue.consume_one([&](int value)
      {
         if (value < 0)
         {
            PostQuitMessage(WM_QUIT);
         }
         else
         {
            int result = value - 1;
            std::cout << "Final result is " << result << " on thread " << std::this_thread::get_id() << std::endl;
         }
      });
   }

private:
   void ProcessInputData()
   {
      bool shutdown = false;

      //Worker Thread processes input data indefinitely
      do
      {
         _inputQueue.consume_one([&](std::shared_ptr<Task> task)
         {    
            std::cout << "Getting element from input queue on thread " << std::this_thread::get_id() << std::endl;           

            if (task->IsShutdownRequest()) { shutdown = true; }

            int result = task->Execute();
            _outputQueue.push(result);
         });

      } while (shutdown == false);
   }

   std::thread _workerThread;
   boost::lockfree::spsc_queue<std::shared_ptr<Task>,   boost::lockfree::capacity<1024>> _inputQueue;
   boost::lockfree::spsc_queue<int, boost::lockfree::capacity<1024>> _outputQueue;
};


std::shared_ptr<Manager> g_pMgr;


//timer to force Main Thread to process Manager's output queue
void CALLBACK TimerCallback(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime)
{
   if (nIDEvent == ProcessDataCheckTimerID)
   {
      KillTimer(NULL, ProcessDataCheckPeriodInMilliseconds);
      ProcessDataCheckTimerID = 0;

      //call function to process data
      g_pMgr->ProcessOutputData();

      //reset timer
      ProcessDataCheckTimerID = SetTimer(NULL, ProcessDataCheckTimerID, ProcessDataCheckPeriodInMilliseconds, (TIMERPROC)&TimerCallback);
   }
}


int main()
{
   std::cout << "Main thread is " << std::this_thread::get_id() << std::endl;

   g_pMgr = std::make_shared<Manager>();

   ProcessDataCheckTimerID = SetTimer(NULL, ProcessDataCheckTimerID, ProcessDataCheckPeriodInMilliseconds, (TIMERPROC)&TimerCallback);

   //queue up some dummy data
   for (int i = 1; i <= 10; i++)
   {
      g_pMgr->QueueData(i);
   }

   //queue a shutdown request
   g_pMgr->QueueData(-1);

   //fake the application's message loop
   MSG msg;
   bool shutdown = false;
   while (shutdown == false)
   {
      if (GetMessage(&msg, NULL, 0, 0))
      {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
      else   
      {
         shutdown = true;
      }
   }

   return 0;
}