信号和线程 - 好还是坏的设计决策?

发布于 2024-09-05 05:33:36 字数 3576 浏览 2 评论 0原文

我必须编写一个执行高度计算密集型计算的程序。该程序可能会运行几天。 计算可以很容易地分离在不同的线程中,而不需要共享数据。 我想要一个 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;
}

I have to write a program that performs highly computationally intensive calculations. The program might run for several days.
The calculation can be separated easily in different threads without the need of shared data.
I want a GUI or a web service that informs me of the current status.

My current design uses BOOST::signals2 and BOOST::thread.
It compiles and so far works as expected.
If a thread finished one iteration and new data is available it calls a signal which is connected to a slot in the GUI class.

My question(s):

  • Is this combination of signals and threads a wise idea? I another forum somebody advised someone else not to "go down this road".
  • Are there potential deadly pitfalls nearby that I failed to see?
  • Is my expectation realistic that it will be "easy" to use my GUI class to provide a web interface or a QT, a VTK or a whatever window?
  • Is there a more clever alternative (like other boost libs) that I overlooked?

following code compiles with

g++ -Wall -o main -lboost_thread-mt <filename>.cpp

code follows:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

笑咖 2024-09-12 05:33:36

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

看来是有道理的。您能提供另一个线程的链接吗?他们有解释他们的理由吗?

附近是否存在我没​​有发现的潜在致命陷阱?

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

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

这并不容易。要解决此问题,您必须使通知切换线程上下文。这就是我要做的:

让您的 GuiClass 成为一个抽象基类,实现它自己的消息队列。当您的线程调用 GuiClass::slot_data_changed 时,您会锁定互斥体并将收到的通知的副本发布到内部(private:)消息队列上。在 GuiClass 的线程中,您创建一个函数来锁定互斥体并在队列中查找通知。该函数应该在客户端代码的线程中运行(在您从抽象 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,一个 A 类 的实例 [在线程 tA 中运行] 执行一些操作,以及 b,它是一个B 类 [在线程 tB 的上下文中运行] 和 b 想要告诉 a 一些事情。

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

要更改此设置(让 a.something() 在 tA 的上下文中运行),您必须切换线程上下文。这意味着 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 实现了,所以如果您使用它,则不必自己实现它。

Is this combination of signals and
threads a wise idea? I another forum
somebody advised someone else not to
"go down this road".

It seems to be sound. Can you provide a link to the other thread? Were they explaining their reasoning?

Are there potential deadly pitfalls nearby that I failed to see?

If they are I fail to see them also. What you need to take care of is that the notifications are thread-safe (the triggering of the signal doesn't switch thread contexts, to your GuiClass::slot_data_changed should be called from all the other threads.

Is my expectation realistic that it will be "easy" to use my GUI class to provide a web interface or a QT, a VTK or a whatever window?

It will not be easy. To fix this, you'd have to make your notification switch threading contexts. Here's what I would do:

Have your GuiClass be an abstract base class, implementing it's own message queue. When GuiClass::slot_data_changed is called by your threads, you lock a mutex and post a copy of the received notification on an internal (private:) message queue. In the thread of the GuiClass you create a function that locks the mutex and looks for notifications in the queue. This function should run in the client code's thread (in the thread of the concrete classes you specialize from the abstract GuiClass).

Advantages:

  • your base class encapsulates and isolates the thread context switching, transparently to it's specializations.

Disadvantages:

  • your client code has to either run the polling method or allow it to run (as a thread-processing function).

  • it's a bit complicated :)

Is my expectation realistic that it
will be "easy" to use my GUI class to
provide a web interface or a QT, a VTK
or a whatever window?

Doesn't see so, but it's not so easy. Besides the thread context-switching there may be other issues I'm missing at the moment.

Is there a more clever alternative
(like other boost libs) that I
overlooked?

Not other boost libs, but the way you wrote your threads is not good: the joins are made sequentially in your code. To have only one join for all threads, use a boost::thread_group.

Instead of:

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();

you will have:

boost::thread_group processors;
processors.create_thread(r1, 3);
// the other threads here

processors.join_all();

Edit: A thread context is everything that is specific to a particular running thread (thread-specific storage, the stack of that thread, any exceptions thrown in that thread's context and so on).

When you have various thread contexts in the same application (multiple threads) you need to synchronize access to resources created within a thread context and accessed from different threads (using locking primitives).

For example, let's say you have a, an instance of class A [running in thread tA] doing some stuff and b, an instance of class B [running in the context of thread tB] and b wants to tell a something.

The "wants to tell a something" part means that b wants to call a.something() and a.something() will be called in the context of tB (on the stack of thread B).

To change this (to have a.something() run in the context of tA), you have to switch the thread context. This means that instead of b telling a "run A::something()", b tells a "run A::something()` in your own thread context".

Classical implementation steps:

  • b sends a message to a from within tB

  • a polls for messages from within tA

  • When a finds the message from b, it runs a.something() itself, within tA.

This is the switching of threading contexts (the execution of A::something will be executed in tA instead of tB, as it would have been if called directly from b).

From the link you provided, it seems this is already implemented by boost::asio::io_service, so if you use that, you don't have to implement it yourself.

风吹短裙飘 2024-09-12 05:33:36

有一个非常重要的陷阱:

据我了解 signals2 的线程安全性 槽在信令线程中运行。大多数 GUI 库(尤其是 Qt 和 OpenGL)必须从单个线程完成所有绘图。这一般来说是没有问题的,但是需要注意一下。你有两个选择:

第一个是你要小心,不要在 GuiClass::slot_data_changed 内部进行任何绘图(因为你使用 Qt 看看 QCoreApplication::postEvent (抱歉不允许发布 Qt 文档的链接))。

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

There is one very important pitfall:

As far as I understand signals2's thread safety the slots are run in the signalling thread. Most GUI libraries (especially Qt and OpenGL) must do all drawing from a single thread. This is no problem in general, but it takes a bit of care. You have two options:

The first is that you are careful that you do not do any drawing inside of GuiClass::slot_data_changed (since you use Qt have a look at QCoreApplication::postEvent ( sorry not allowed to post link to Qt docu)).

The second is that you build a message queue yourself, which saves the slot invocations and executes them in the GUI thread. This is somewhat more onerous, but also safer, because your GUI Classes can be written without caring about thread safety.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文