我必须编写一个执行高度计算密集型计算的程序。该程序可能会运行几天。 可以在不同的线程中轻松分离计算,而无需共享数据。 我想要一个GUI或一个Web服务,告诉我当前的状态。
我目前的设计使用BOOST :: signals2和BOOST :: thread。 它编译并到目前为止按预期工作。 如果一个线程完成一次迭代并且有新数据可用,则它会调用一个连接到GUI类中的插槽的信号。
我的问题:
以下代码使用
进行编译g++ -Wall -o main -lboost_thread-mt <filename>.cpp
代码如下:
#include <boost/signals2.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <iterator>
#include <string>
using std::cout;
using std::cerr;
using std::string;
/**
* Called when a CalcThread finished a new bunch of data.
*/
boost::signals2::signal<void(string)> signal_new_data;
/**
* The whole data will be stored here.
*/
class DataCollector
{
typedef boost::mutex::scoped_lock scoped_lock;
boost::mutex mutex;
public:
/**
* Called by CalcThreads call the to store their data.
*/
void push(const string &s, const string &caller_name)
{
scoped_lock lock(mutex);
_data.push_back(s);
signal_new_data(caller_name);
}
/**
* Output everything collected so far to std::out.
*/
void out()
{
typedef std::vector<string>::const_iterator iter;
for (iter i = _data.begin(); i != _data.end(); ++i)
cout << " " << *i << "\n";
}
private:
std::vector<string> _data;
};
/**
* Several of those can calculate stuff.
* No data sharing needed.
*/
struct CalcThread
{
CalcThread(string name, DataCollector &datcol) :
_name(name), _datcol(datcol)
{
}
/**
* Expensive algorithms will be implemented here.
* @param num_results how many data sets are to be calculated by this thread.
*/
void operator()(int num_results)
{
for (int i = 1; i <= num_results; ++i)
{
std::stringstream s;
s << "[";
if (i == num_results)
s << "LAST ";
s << "DATA " << i << " from thread " << _name << "]";
_datcol.push(s.str(), _name);
}
}
private:
string _name;
DataCollector &_datcol;
};
/**
* Maybe some VTK or QT or both will be used someday.
*/
class GuiClass
{
public:
GuiClass(DataCollector &datcol) :
_datcol(datcol)
{
}
/**
* If the GUI wants to present or at least count the data collected so far.
* @param caller_name is the name of the thread whose data is new.
*/
void slot_data_changed(string caller_name) const
{
cout << "GuiClass knows: new data from " << caller_name << std::endl;
}
private:
DataCollector & _datcol;
};
int main()
{
DataCollector datcol;
GuiClass mc(datcol);
signal_new_data.connect(boost::bind(&GuiClass::slot_data_changed, &mc, _1));
CalcThread r1("A", datcol), r2("B", datcol), r3("C", datcol), r4("D",
datcol), r5("E", datcol);
boost::thread t1(r1, 3);
boost::thread t2(r2, 1);
boost::thread t3(r3, 2);
boost::thread t4(r4, 2);
boost::thread t5(r5, 3);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
datcol.out();
cout << "\nDone" << std::endl;
return 0;
}
答案 0 :(得分:12)
这是信号和信号的组合吗? 一个明智的想法?我是另一个论坛 有人建议其他人不要 “走这条路”。
似乎是合理的。你能提供其他线程的链接吗?他们在解释他们的推理吗?
附近是否有潜在的致命陷阱,我没有看到?
如果他们是我也看不到他们。您需要注意的是通知是线程安全的(触发信号不会切换线程上下文,应该从所有其他线程调用GuiClass::slot_data_changed
。
我的期望是否真实,使用我的GUI类提供Web界面或QT,VTK或任何窗口都会“轻松”?
这并不容易。要解决此问题,您必须使用通知切换线程上下文。这就是我要做的事情:
让您的GuiClass
成为一个抽象基类,实现它自己的消息队列。当您的线程调用GuiClass::slot_data_changed
时,您将锁定互斥锁并在内部(private:
)消息队列中发布已接收通知的副本。在GuiClass
的线程中,您创建了一个锁定互斥锁并在队列中查找通知的函数。此函数应该在客户端代码的线程中运行(在您从abstract GuiClass
专门化的具体类的线程中)。
优点:
缺点:
您的客户端代码必须运行轮询方法或允许它运行(作为线程处理函数)。
这有点复杂:)
我的期望是否真实 将“轻松”使用我的GUI类 提供Web界面或QT,VTK 或者什么窗口?
看不到,但这并不容易。除了线程上下文切换之外,我可能还有其他问题。
是否有更聪明的选择 (就像其他提升库)我 忽略了?
不是其他的升级库,但是编写线程的方式并不好:连接是在代码中按顺序进行的。要为所有线程只有一个join
,请使用boost :: thread_group。
而不是:
boost::thread t1(r1, 3);
boost::thread t2(r2, 1);
boost::thread t3(r3, 2);
boost::thread t4(r4, 2);
boost::thread t5(r5, 3);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
你将拥有:
boost::thread_group processors;
processors.create_thread(r1, 3);
// the other threads here
processors.join_all();
编辑:线程上下文是特定于特定运行线程的所有内容(特定于线程的存储,该线程的堆栈,该线程上下文中抛出的任何异常等)。< / p>
当您在同一个应用程序(多个线程)中有各种线程上下文时,您需要同步对线程上下文中创建的资源的访问,并从不同的线程访问(使用锁定原语)。
例如,假设您有a
,class A
[在线程tA中运行]执行某些操作的实例b
,class B
的实例[正在运行]在线程tB的上下文中,b
想要告诉a
某事。
“想要告诉a
某事”部分意味着b
想要调用a.something()
,而a.something()
将在tB的上下文中调用(在{线程B)。
要更改此项(要在tA的上下文中运行a.something()
),您必须切换线程上下文。这意味着代替b
告诉a
“运行A::something()
”,b
告诉a
“在您自己的线程上下文中运行A :: something()` ”
经典实施步骤:
b
从tB内向a
发送消息
a
轮询来自tA
当a
找到来自b
的消息时,它会在tA内运行a.something()本身。
这是线程上下文的切换(A::something
的执行将以tA而不是tB执行,就像直接从b
调用一样。
从您提供的链接看,这似乎已由boost::asio::io_service
实现,因此如果您使用该链接,则无需自行实施。
答案 1 :(得分:7)
有一个非常重要的陷阱:
据我了解signals2's thread safety,广告位在信号主题中运行。大多数GUI库(尤其是Qt和OpenGL)必须从单个线程完成所有绘图。一般来说这不是问题,但需要一点小心。您有两种选择:
首先,你要注意不要在GuiClass::slot_data_changed
内进行任何绘图(因为你使用Qt看看QCoreApplication :: postEvent(抱歉不允许发布链接到Qt文档))。
第二个是您自己构建一个消息队列,它保存了插槽调用并在GUI线程中执行它们。这有点繁重,但也更安全,因为您的GUI类可以在不关心线程安全的情况下编写。