从其他线程访问 QObject 子类

Accessing QObject subclasses from other threads

我需要一些帮助来理解从其他线程访问 QObject 子classes 的细节。

来自 Qt documentation:

“如果您在不在当前线程中的 QObject subclass 上调用函数并且该对象可能会接收事件,则必须保护对您的 QObject subclass 的内部数据带有互斥锁;否则,您可能会遇到崩溃或其他意外行为。"

考虑以下 classes:

// Created on stack in main() => thread affinity = main thread
class Model : public QObject {
    Q_OBJECT
public:
    int getValue() const {
        return m_value;
    }
signals:
    void error(QString err);
public slots:
    void incrementValue() { 
        m_value++; 
    };
    void threadError(QString err) {
        emit error(err);
    }
private:
    std::atomic<int> m_value = 0;
}


class Thread : public QThread {
    Q_OBJECT
public:
    Thread(Model* model) : QThread(), m_model(model) {}
signals:
    void incrementValue();
    void error(QString err);
private:
    void run() override; // calls m_model->getValue(), emits incrementValue()
    Model* m_model;
};

还假设有一个 Widget class 和一个 QPushButton,单击时调用 incrementValue() 插槽。线程偶尔也会发出 incrementValue 信号,它连接到 incrementValue() 插槽。

存在潜在的数据竞争: incrementValue() 可以通过用户从非主线程 Thread::run() 单击 QPushButton 作为 getValue() 同时从主线程调用。 然而,由于 m_value 变量是原子的,所以这是安全的吗?

我想这归结为“QObject 子class 的内部数据”的构成。 在我的示例中 m_value 显然是内部数据。 还有什么? Q_OBJECT 宏是否扩展为我需要互斥保护的变量?

上面没有任何内容是特定于 QObject 的。 换句话说,我对多线程有同样的潜在问题,就好像模型没有从 QObject 继承一样。 Qt 没有为我修复这种情况下的多线程问题,但也没有引入任何新问题。 这是正确的吗?

为了简化讨论: 模型和线程都尽可能纯 C++。 我不在 Thread 或任何其他 Qt 中使用 QTimer、QTcpSocket、事件循环。我也不调用 moveToThread。在这种情况下,我唯一使用 Qt 的是它的 signal/slot 机制(和 QString)。

我看到的一个可能但麻烦的解决方案是: 将模型重写为 NOT subclass QObject。 介绍:

class Gui2Model : public QObject {
Q_OBJECT
public:
   Gui2Model(Model* model) : m_model(model) {}
signals:
   void error(QString err);
public slots:
   void incrementValue() {
      m_model->incrementValue();
   }
   void errorSlot(QString err) {
     emit error(err);
   }
private:
   Model* m_model;
};

将增量 QPushButton clicked 信号连接到 Gui2Model::incrementValue() 插槽。

通过调用从 Thread 发出错误信号:

void Model::error(QString err) {
    QMetaObject::invokeMethod(m_gui2model, "errorSlot", Qt::QueuedConnection, Q_ARG(QString, err));
}

这样我就承担了同步模型的全部负担,但至少它是根据我(应该)从标准 C++ 知道的规则发生的。

Is it sufficient to rewrite the m_value variable to be std::atomic?

假设你在模型销毁之前加入线程,我认为是的。

There is nothing in the above that is specific to QObject.

我认为他们想强调“对象可能接收事件”部分,这可能不是您代码中的直接调用,而是 signal/slot 连接。 signal/slot 连接将特定于 QObject。

在一个非常扭曲的场景中,您可能同时收到来自您的线程的信号和来自您的线程的直接调用,这会产生竞争条件:-)

As a rule it may perhaps be OK to pass const Model* to the Thread object. It should be safe to call const methods on the Model since these do not change the state of the Model?

如果模型是常量创建的,那么是的。实际上,在这种情况下没有太多选择。但是,如果模型是创建的 non-const,并且从您的线程访问它时主线程的值可能会发生变化,那么对它的访问仍然需要同步。