Безопасно ли удалять выделенный объект кучи внутри слота в разных потоках? - PullRequest
1 голос
/ 28 апреля 2020

Я пытаюсь проанализировать segfault, который возникает при доступе к выделенному объекту кучи, созданному потоком отправителя и доступным потоком получателя.

Вот краткая версия кода:

#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 здесь не вариант, но действительно ли это безопасно?

Спасибо.

1 Ответ

1 голос
/ 28 апреля 2020

Да, это «безопасно», если вы можете гарантировать, что указатель останется действительным, когда вы получите к нему доступ.

В этом простом примере это выглядит так. Я вижу потенциальную проблему в вашем коде, которая может быть причиной вашего случайного cra sh:

Объекты, управляемые событиями, могут использоваться только в одном потоке. В частности, это относится к механизму таймера и сетевому модулю. Например, вы не можете запустить таймер или подключить сокет к потоку, который не является потоком объекта.

из https://doc.qt.io/qt-5/threads-qobject.html абзац Повторный вход объекта.

Это то, что вы делаете: объект отправителя создается в главном потоке и включается, поэтому таймер запускается в этом потоке, а затем перемещается в рабочий поток.

Другое дело, что я не на 100% Уверен в себе: вы выполняете соединение перед перемещением объекта в другой поток. По умолчанию, если ничего не сказано о соединении, когда два объекта находятся в одном потоке, это прямое соединение, а когда объект находится в другом потоке, это, очевидно, соединение в очереди. Я не знаю, в какой степени Qt устойчив к изменению типа соединения при перемещении объекта, но я бы предпочел сначала переместить объект, а затем подключить его.

...