草庐IT

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

coder 2024-02-06 原文

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

我目前的设计使用 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;
}

最佳答案

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?

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

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?

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

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

优点:

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

缺点:

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

  • 有点复杂:)

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?

不是其他 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();

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

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

例如,假设您有 aA 类 的一个实例 [在线程 tA 中运行] 做一些事情,bB 类 的实例 [在线程 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 实现,因此如果您使用它,则不必自己实现。

关于c++ - 信号和线程——好的还是坏的设计决策?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3021854/

有关c++ - 信号和线程——好的还是坏的设计决策?的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  3. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  4. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  5. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  6. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  7. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  8. ruby - 如何让Ruby捕获线程中的语法错误 - 2

    我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

  9. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  10. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

随机推荐