6

我必须编写一个执行高度计算密集型计算的程序。该程序可能会运行几天。计算可以很容易地在不同的线程中分离,而不需要共享数据。我想要通知我当前状态的 GUI 或 Web 服务。

我当前的设计使用 BOOST::signals2 和 BOOST::thread。它编译并且到目前为止按预期工作。如果一个线程完成了一次迭代并且有新数据可用,它会调用一个连接到 GUI 类中的插槽的信号。

我的问题:

  • 这种信号和线程的组合是一个明智的想法吗?我另一个论坛有人建议其他人不要“走这条路”。
  • 附近是否有我没有看到的潜在致命陷阱?
  • 我的期望是否现实,使用我的 GUI 类来提供 Web 界面或 QT、VTK 或任何窗口将是“容易的”?
  • 是否有我忽略的更聪明的替代方案(如其他提升库)?

以下代码编译为

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;
}
4

2 回答 2

13

这种信号和线程的组合是一个明智的想法吗?我另一个论坛有人建议其他人不要“走这条路”。

似乎是健全的。你能提供另一个线程的链接吗?他们在解释他们的理由吗?

附近是否有我没有看到的潜在致命陷阱?

如果他们是我也看不到他们。您需要注意的是通知是线程安全的(信号的触发不会切换线程上下文,您GuiClass::slot_data_changed应该从所有其他线程调用。

我的期望是否现实,使用我的 GUI 类来提供 Web 界面或 QT、VTK 或任何窗口将是“容易的”?

没那么简单。要解决此问题,您必须让通知切换线程上下文。这是我要做的:

让你GuiClass成为一个抽象基类,实现它自己的消息队列。当GuiClass::slot_data_changed您的线程调用时,您锁定一个互斥锁并将收到的通知的副本发布到内部 ( private:) 消息队列中。在您的线程中,GuiClass您创建了一个锁定互斥锁并在队列中查找通知的函数。此函数应在客户端代码的线程中运行(在您从 abstract 专门化的具体类的线程中GuiClass)。

好处:

  • 您的基类封装和隔离线程上下文切换,对其专业化透明。

缺点:

  • 您的客户端代码必须运行轮询方法或允许它运行(作为线程处理函数)。

  • 这有点复杂:)

我的期望是否现实,使用我的 GUI 类来提供 Web 界面或 QT、VTK 或任何窗口将是“容易的”?

不这么看,但这并不容易。除了线程上下文切换之外,我目前可能还缺少其他问题。

是否有我忽略的更聪明的替代方案(如其他提升库)?

不是其他 boost 库,但是您编写线程的方式并不好:连接是在您的代码中按顺序进行的。要所有线程只有一个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();

编辑:线程上下文是特定于特定运行线程的所有内容(特定于线程的存储、该线程的堆栈、该线程上下文中引发的任何异常等等)。

当您在同一个应用程序(多个线程)中有各种线程上下文时,您需要同步对在线程上下文中创建并从不同线程访问的资源的访问(使用锁定原语)。

例如,假设您有a一个class A[running in thread tA] 的实例在做一些事情,并且b,一个class B[running in the context of thread tB]的实例b想要告诉a一些事情。

“想要告诉a某事”部分意味着b想要调用a.something()并将a.something()在 tB 的上下文中调用(在线程 B 的堆栈上)。

要改变这一点(a.something()在 tA 的上下文中运行),您必须切换线程上下文。这意味着不是b告诉a“运行A::something()”,而是b告诉“a在你自己的线程上下文中运行 A::something()`”。

经典实现步骤:

  • ba从 tB 内发送消息到

  • a轮询来自 tA 内的消息

  • a找到来自 的消息时b,它会在 tA 内运行 a.something() 本身。

这是线程上下文的切换( 的执行A::something将在 tA 而不是 tB 中执行,就像直接从 调用一样b)。

从您提供的链接来看,这似乎已经由 实现boost::asio::io_service,所以如果您使用它,您不必自己实现它。

于 2010-06-11T11:17:38.137 回答
7

有一个非常重要的陷阱:

据我了解信号2的线程安全,插槽在信号线程中运行。大多数 GUI 库(尤其是 Qt 和 OpenGL)必须从单个线程中完成所有绘图。这一般没有问题,但需要一点小心。你有两个选择:

首先是你小心不要在里面做任何绘图GuiClass::slot_data_changed(因为你使用 Qt 看看 QCoreApplication::postEvent (抱歉不允许发布到 Qt 文档的链接))。

第二种是你自己建立一个消息队列,它保存槽调用并在GUI线程中执行它们。这有点麻烦,但也更安全,因为您的 GUI 类可以在不关心线程安全的情况下编写。

于 2010-06-11T12:39:24.147 回答