Qt 的对象线程亲和性(Thread Affinity)规则
Qt 的线程模型基于 QObject
的线程亲和性(Thread Affinity),它决定了:
对象属于哪个线程(创建它的线程)。
对象的信号槽、事件处理在哪个线程执行。
1. 基本规则
(1)QObject
必须在创建它的线程中使用
每个
QObject
(及其子类,如QTimer
、QWebSocket
)绑定到创建它的线程。不能直接跨线程调用它的方法,否则可能导致崩溃或未定义行为。
(2)子对象继承父对象的线程亲和性
如果
QObject
有父对象,它默认继承父对象的线程。例如:
cpp
QObject *parent = new QObject; // 主线程创建 QTimer *child = new QTimer(parent); // child 也属于主线程
(3)moveToThread()
可以改变线程亲和性
使用
QObject::moveToThread(QThread *)
可以将对象迁移到另一个线程。必须在原线程调用,不能在目标线程直接调用。
2. 跨线程访问 QObject
的正确方式
方法1:信号槽(Qt::QueuedConnection
)
使用
Qt::QueuedConnection
(默认跨线程就是QueuedConnection
)确保槽函数在目标线程执行:cpp
// Worker 线程发送信号 emit requestData("Hello"); // 主线程连接信号槽(自动 QueuedConnection) connect(worker, &Worker::requestData, mainObj, &MainObject::handleData);
方法2:QMetaObject::invokeMethod
动态调用方法,确保在正确的线程执行:
cpp
QMetaObject::invokeMethod( targetObj, "setText", Qt::QueuedConnection, Q_ARG(QString, "Hello") );
方法3:moveToThread()
迁移对象
将
QObject
移到目标线程,之后所有信号槽都在新线程执行:cpp
QThread *workerThread = new QThread; worker->moveToThread(workerThread); // 必须在原线程调用 workerThread->start();
3. 错误示例
错误1:直接跨线程调用方法
cpp
// 主线程创建
QTimer *timer = new QTimer;
// Worker 线程直接调用(危险!)
timer->start(1000); // 可能崩溃!
错误2:跨线程修改 GUI 对象
cpp
// Worker 线程直接修改 QLabel(错误!)
label->setText("Hello"); // 必须用信号槽或 invokeMethod
4. 特殊情况
(1)QObject
没有父对象时
如果没有父对象,
QObject
不会自动删除,需要手动管理内存。如果父对象被移动到另一个线程,子对象也会一起移动。
(2)QObject::deleteLater()
用于安全删除对象:
cpp
obj->deleteLater(); // 在对象所属线程的事件循环中删除
(3)QCoreApplication::postEvent()
可以跨线程发送事件,Qt 会自动处理线程亲和性。
5. 如何检查线程亲和性?
cpp
qDebug() << obj->thread(); // 返回对象所属的 QThread
6.线程安全的 Qt 对象与非线程安全的 Qt 对象
6.1线程安全的 Qt 对象
Qt 中有少量类是显式声明为线程安全的,例如:
QMutex
、QMutexLocker
(线程同步)QReadWriteLock
、QSemaphore
QAtomicInt
、QAtomicPointer
(原子操作)QWaitCondition
QThreadStorage
这些对象可以在任何线程直接使用,无需额外处理。
6.2非线程安全的 Qt 对象(如 QObject
及其子类)
大多数 Qt 对象(如 QWebSocket
、QTcpSocket
、QTimer
)是 QObject
的子类,并且不是线程安全的。
它们的规则是:
必须在创建它们的线程中使用(线程亲和性)。
不能直接跨线程调用方法,否则可能导致崩溃或未定义行为。
6.3如何判断一个 Qt 对象是否线程安全?
查阅 Qt 官方文档,如果类文档明确说明它是 thread-safe,则可以跨线程使用。
默认情况下,所有
QObject
子类都不是线程安全的,除非特别说明(如QMutex
)。基本数据类型(如
int
、QString
)本身是线程安全的,但多线程访问时仍需同步(如用QMutex
)。
7. 总结
规则 | 说明 |
---|---|
QObject 属于创建它的线程 |
不能直接跨线程调用方法 |
子对象继承父对象的线程 | 除非显式调用 moveToThread() |
跨线程通信用信号槽或 invokeMethod |
确保操作在正确线程执行 |
moveToThread() 必须在原线程调用 |
不能跨线程直接迁移对象 |
8. 完整示例:QWebSocket
跨线程通信(使用 moveToThread
)
以下是一个完整的跨线程 QWebSocket
示例,演示如何:
在主线程创建
QWebSocket
迁移到工作线程
通过信号槽安全通信
完整代码
(1)WebSocketManager.h
(管理 QWebSocket
)
cpp
#pragma once
#include <QObject>
#include <QWebSocket>
class WebSocketManager : public QObject {
Q_OBJECT
public:
explicit WebSocketManager(QObject *parent = nullptr);
~WebSocketManager();
public slots:
void connectToServer(const QUrl &url);
void sendMessage(const QString &message);
void closeSocket();
signals:
void connected();
void disconnected();
void messageReceived(const QString &msg);
private slots:
void onConnected();
void onDisconnected();
void onTextMessageReceived(const QString &message);
private:
QWebSocket *m_socket;
};
(2)WebSocketManager.cpp
cpp
#include "WebSocketManager.h"
WebSocketManager::WebSocketManager(QObject *parent) : QObject(parent) {
m_socket = new QWebSocket();
connect(m_socket, &QWebSocket::connected, this, &WebSocketManager::onConnected);
connect(m_socket, &QWebSocket::disconnected, this, &WebSocketManager::onDisconnected);
connect(m_socket, &QWebSocket::textMessageReceived, this, &WebSocketManager::onTextMessageReceived);
}
WebSocketManager::~WebSocketManager() {
m_socket->deleteLater(); // 安全删除
}
void WebSocketManager::connectToServer(const QUrl &url) {
m_socket->open(url);
}
void WebSocketManager::sendMessage(const QString &message) {
m_socket->sendTextMessage(message);
}
void WebSocketManager::closeSocket() {
m_socket->close();
}
void WebSocketManager::onConnected() {
emit connected();
}
void WebSocketManager::onDisconnected() {
emit disconnected();
}
void WebSocketManager::onTextMessageReceived(const QString &message) {
emit messageReceived(message);
}
(3)WorkerThread.h
(工作线程)
cpp
#pragma once
#include <QThread>
#include "WebSocketManager.h"
class WorkerThread : public QThread {
Q_OBJECT
public:
explicit WorkerThread(QObject *parent = nullptr);
~WorkerThread();
WebSocketManager *socketManager() const { return m_socketManager; }
protected:
void run() override;
private:
WebSocketManager *m_socketManager;
};
(4)WorkerThread.cpp
cpp
#include "WorkerThread.h"
WorkerThread::WorkerThread(QObject *parent) : QThread(parent) {
m_socketManager = new WebSocketManager();
}
WorkerThread::~WorkerThread() {
quit();
wait();
m_socketManager->deleteLater(); // 安全删除
}
void WorkerThread::run() {
// 将 WebSocketManager 移到当前线程
m_socketManager->moveToThread(this->thread());
exec(); // 启动事件循环
}
(5)MainWindow.h
(主线程 UI)
cpp
#pragma once
#include <QMainWindow>
#include <QPushButton>
#include <QTextEdit>
#include "WorkerThread.h"
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onConnectClicked();
void onSendClicked();
void onSocketMessage(const QString &msg);
private:
QPushButton *m_connectBtn;
QPushButton *m_sendBtn;
QTextEdit *m_logEdit;
WorkerThread *m_workerThread;
WebSocketManager *m_socketManager;
};
(6)MainWindow.cpp
cpp
#include "MainWindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
// UI 初始化
m_connectBtn = new QPushButton("Connect", this);
m_sendBtn = new QPushButton("Send", this);
m_logEdit = new QTextEdit(this);
// 布局代码略...
// 创建工作线程和 WebSocketManager
m_workerThread = new WorkerThread(this);
m_socketManager = m_workerThread->socketManager();
// 连接信号槽
connect(m_connectBtn, &QPushButton::clicked, this, &MainWindow::onConnectClicked);
connect(m_sendBtn, &QPushButton::clicked, this, &MainWindow::onSendClicked);
connect(m_socketManager, &WebSocketManager::messageReceived, this, &MainWindow::onSocketMessage);
// 启动工作线程
m_workerThread->start();
}
MainWindow::~MainWindow() {
m_workerThread->quit();
m_workerThread->wait();
}
void MainWindow::onConnectClicked() {
QUrl url("ws://echo.websocket.org"); // 测试服务器
QMetaObject::invokeMethod(m_socketManager, "connectToServer", Qt::QueuedConnection, Q_ARG(QUrl, url));
}
void MainWindow::onSendClicked() {
QString msg = "Hello WebSocket!";
QMetaObject::invokeMethod(m_socketManager, "sendMessage", Qt::QueuedConnection, Q_ARG(QString, msg));
}
void MainWindow::onSocketMessage(const QString &msg) {
m_logEdit->append("Received: " + msg);
}
关键点说明
QWebSocket
只能在所属线程操作通过
moveToThread()
将其迁移到工作线程。
跨线程通信必须用信号槽或
invokeMethod
主线程通过
QueuedConnection
调用WebSocketManager
的方法。
线程安全退出
使用
deleteLater
和quit() + wait()
确保资源正确释放。