我是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循环,并在排队数据之前调用它。数据在工作线程上排队并计算,但结果永远不会被发送回主线程。
答案 0 :(得分:1)
这是一个问题
Signal signal;
// ...
_workerService.post(std::bind(&Manager::ProcessData, this, boost::ref(signal)));
您将任务绑定到之后立即停止的本地变量。您的计划会调用Undefined Behaviour。
这也是
void workerMain()
{
std::cout << "Worker thread: " << std::this_thread::get_id() << std::endl;
_workerService.poll();
}
poll
将返回,因此线程会过早退出。使用run()
保留线程,直到重置_workerWork
。
最大问题 是您将无限处理循环发布到事件队列中。 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负载。
最后,您需要异步StartEventLoop
,否则它将无法返回。让它在一个单独的线程上运行似乎是一个选项,但很可能是违反spsc_queue
的线程模型要求的方法...请注意这里。
要么 需要低延迟的高吞吐量处理,在这种情况下你根本不需要asio,只需在{{1上进行一次workerThread循环}}
<强> Live Demo 强>
见下面的列表
或 您希望在隔离线程上进行低成本排队和处理。在这种情况下,使用一个老式的锁定队列,例如
consume_one