【问题标题】:Is it safe to delete a heap allocated object inside a slot across different threads?跨不同线程删除插槽内的堆分配对象是否安全?
【发布时间】:2020-04-28 17:36:43
【问题描述】:

我正在尝试分析在访问由发送方线程创建并由接收方线程访问的堆分配对象时似乎发生的段错误。

这是代码的简短版本:

#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <QTimer>

class Data
{
public:
    Data(int data1) : m_data1(data1) {}
    int data1() {
        return m_data1;
    }
private:
    int m_data1;
};

class Sender : public QObject
{
    Q_OBJECT

public:
    Sender(int timeout) : m_timeout(timeout) {}

public slots:
    void startSendingDatas() {
        QTimer::singleShot(m_timeout, [this]() {
            emit datas(new Data(3));
        });
    }

signals:
    void datas(Data *data);

private:
    int m_timeout;
};

class Receiver : public QObject
{
    Q_OBJECT

public slots:
    void onDatas(Data *data) {
        qDebug() << "data1 = " << data->data1();
        delete data; // is it always safe ?
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Sender sender(5000);
    Receiver receiver;

    QObject::connect(&sender, SIGNAL(datas(Data*)),
                     &receiver, SLOT(onDatas(Data*)));

    QThread worker;
    worker.start();

    sender.moveToThread(&worker);

    // now we call it asynchronously
    QMetaObject::invokeMethod(&sender, "startSendingDatas");

    return a.exec();
}

#include "main.moc"

Data 不继承自 QObject 所以deleteLater 在这里不是一个选项,但这样做真的安全吗?

谢谢。

【问题讨论】:

  • 对我来说看起来不太安全。另外,不相关的是,您为什么在QObject::connect(&amp;sender, SIGNAL(datas(Data*)), &amp;receiver, SLOT(onDatas(Data*))); 中使用基于旧字符串的SIGNALSLOT 宏?你真的应该使用新的类型安全变体,它会在编译时失败,而不是在运行时在你搞砸时默默地不做任何事情——它们甚至会产生更快的代码作为奖励。
  • 通过作为信号/槽参数传递的指针删除堆分配的对象,您是否看到有关 Qt 的信号/槽机制的任何问题?
  • 在显示的代码中,我看不到任何明显的问题。但是...如果Data 不是 POD 或者发送者继续访问与发送的指针相关联的内存,那么显然会有问题。与多线程一样,魔鬼在细节中。另外,如果datas信号连接了多个槽怎么办?
  • datas 在这里连接到一个唯一的插槽,但如果多个插槽调用delete,则可以肯定此模式将不起作用。
  • 通过信号槽连接发送单一所有权指针是灾难的根源。一个信号可能附加了零个或多个监听器。这就是信号的使用方式。如果没有侦听器会泄漏内存,或者在连接两个插槽时导致双重删除的信号 - 只是坏消息。为了传递所有权,您需要使用事件,因为它们保证被销毁一次,即使没有人接受该事件。如果您坚持使用信号槽连接,请使用 ref 计数共享对象或 std::shared_ptr。

标签: c++ linux qt signals-slots


【解决方案1】:

是的,如果您可以保证指针在您访问它时仍然有效,那么这样做是“安全的”。

在这个简单的例子中似乎是这样。我在您的代码中发现了一个潜在问题,这可能是您随机崩溃的原因:

事件驱动的对象只能在单个线程中使用。具体来说,这适用于定时器机制和网络模块。例如,您不能在不是对象线程的线程中启动计时器或连接套接字。

来自https://doc.qt.io/qt-5/threads-qobject.html段落对象重入。

这就是你正在做的:在主线程上创建发送者对象,然后在这个线程上启动计时器,然后将它移动到工作线程。

我不是 100% 确信的另一件事:在将对象移动到另一个线程之前执行连接。默认情况下,如果没有说明连接,当两个对象在同一个线程上时,它是直接连接,当对象在不同线程上时,它显然是一个排队连接。我不知道 Qt 在移动对象时更改连接类型到哪个扩展是健壮的,但我宁愿先移动对象然后连接它。

【讨论】:

  • 其实QTimer在实际代码中是不用的。在这个小例子中,它只是在事件循环启动后触发信号的一种简单方法。
猜你喜欢
  • 2011-01-15
  • 1970-01-01
  • 1970-01-01
  • 2019-05-03
  • 1970-01-01
  • 2017-11-28
  • 2015-09-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多