Qt信号槽线程间通信,这是安全的方式吗?

3

假设我有2个线程: A 和 B,A是主线程。在线程A中,有两个on_button_click插槽。第一个插槽如下:

on_button_one_clicked(){
  myObject_one = new myObject(this);
  myObject_one->setParent(map);
  myObject_two = new myObject(this);
  myObject_two->setParent(map);
  ...
}

第二个是:
on_button_two_clicked(){
  foreach(myObject* i, map->childItems){
    delete i;
  }
}

在这里,myObjectmap都是QGraphicsItem。在线程B中,发出一个信号以触发线程A的槽函数:
slot_triggered_by_signal_from_thread_B(){
  foreach(myObject* i, map->childItems){
    i->do_some_thing();
  }
}

这个安全吗?当代码到达i->do_some_thing这一行并遇到空指针而崩溃时,会发生什么?


始终将所有UI相关的内容放在主线程中,你应该只将需要较长时间(50毫秒或更多)计算的繁重任务放到另一个线程中进行。 - dtech
如果您正在主线程中完成工作,为什么要使用另一个线程呢?使用额外的线程的目的是使主线程保持响应。而且,如果是这种情况,听起来您已经将与 UI 的交互移动到其他线程中,这是不应该做的。 - dtech
@ddriver:我认为do_some_thing()与UI相关,而他在另一个线程中进行了大量计算,例如完成后触发UI操作。在这方面,OP所做的看起来很合理。他所问的是,在可能被“同时”删除的QGIs上执行某些操作是否可以。 - László Papp
1
他提供了一个完全最小化的示例,使混淆清晰明了。很遗憾你没有看到它,但是有效的问题确实存在。继续前进吧。 - László Papp
@ddriver,我想lpapp已经理解我的意思了。虽然我以为这应该是一个简单的短问题... - Nyaruko
显示剩余10条评论
2个回答

3
只要您在线程之间使用自动连接或排队连接类型,就是安全的,因为在这种情况下,只有当您的其他插槽正在完成或反之亦然时,才会调用该插槽。它们不会同时运行。我想这可能是唯一让它不够安全的问题。我相信您是指这样的情况:
#include <QThread>
#include <QApplication>
#include <QTimer>
#include <QObject>
#include <QPushButton>
#include <QDebug>

class Work : public QObject
{
    Q_OBJECT
    public:
        explicit Work(QObject *parent = Q_NULLPTR) : QObject(parent) { QTimer::singleShot(200, this, SLOT(mySlot())); }
    public slots:
        void mySlot() { emit mySignal(); }
    signals:
        void mySignal();
};

class MyApplication : public QApplication
{
    Q_OBJECT
    public:

    explicit MyApplication(int argc, char **argv)
        : QApplication(argc, argv)
        , pushButton(new QPushButton())
    {
        QStringList stringList{"foo", "bar", "baz"};
        QThread *workerThread = new QThread();

        Work *work = new Work();
        work->moveToThread(workerThread);

        connect(pushButton, &QPushButton::clicked, [&stringList] () {
            for (int i = 0; i < stringList.size(); ++i)
                stringList.removeAt(i);
        });
        connect(work, &Work::mySignal, [&stringList] () {
            for (int i = 0; i < stringList.size(); ++i)
                qDebug() << stringList.at(i);           
        });
    }

    ~MyApplication()
    {
        delete pushButton;
    }

    QPushButton *pushButton;
};

#include "main.moc"

int main(int argc, char **argv)
{
    MyApplication application(argc, argv);   
    return application.exec();
}

main.pro

TEMPLATE = app
TARGET = main
QT += widgets
CONFIG += c++11
SOURCES += main.cpp

构建和运行

qmake && make && ./main

但是,如果他为每个项目发出其他异步信号,那么它很可能会成为主要的性能瓶颈。排队的信号很慢,特别是当事件循环被这些信号堵塞时。几年前,我使用了细粒度工作者和排队信号,导致性能急剧下降。 - dtech
在这里使用Qt::UniqueConnection是可以的吗?它会导致程序同时运行吗? - Nyaruko
1
@Nyaruko:正如文档所述,它用于避免同一发送者的同一信号和同一接收者的同一插槽之间存在多个连接。简而言之,这与你手头的问题无关。 - László Papp

0

假设主线程在on_button_two_clicked函数中执行了一些繁重的工作。 任何其他操作,包括用户执行某些操作或来自其他线程的请求 (在这种情况下是slot_triggered_by_signal_from_thread_B)都将被阻塞,直到完成on_button_two_clicked

我认为这意味着它保证了完成先前的事件。 总之,它是安全的!


在我看来,您误解了OP的意思。他并没有在主线程中执行繁重的工作,这也是问题的关键所在。他是在“非”主线程中执行主要工作,并且只是向主线程发出信号以执行一些GUI工作,例如当繁重的工作完成时。 - László Papp
@lpapp - 你确定吗?请阅读他在问题中的第一个(第二个)评论。 - dtech
我认为OP只是担心在slot_triggered_by_signal_from_thread_B中引用对象时,在on_button_two_clicked中删除该对象。这篇文章只是线程安全的,不涉及繁重的工作或UI事项。 - parivana
@parivana - 在我看来,OP遇到了一个不良设计的情况。因此,虽然有些人可能会认为保证不良设计是安全的就足够了,但我不能不觉得他需要的答案是如何改进他的设计。 - dtech
@lpapp,我认为你应该停止暗示。关于问题,关于我需要学习的内容等等。我知道线程是用来干什么的,但你似乎不太了解优雅和灵活的应用程序设计。可能太忙于处理次要的技术细节了吧? - dtech
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接