从其他线程访问 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,并且从您的线程访问它时主线程的值可能会发生变化,那么对它的访问仍然需要同步。
我需要一些帮助来理解从其他线程访问 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,并且从您的线程访问它时主线程的值可能会发生变化,那么对它的访问仍然需要同步。