Qt 的对象线程亲和性规则

发布于:2025-08-11 ⋅ 阅读:(18) ⋅ 点赞:(0)

Qt 的对象线程亲和性(Thread Affinity)规则

Qt 的线程模型基于 QObject 的线程亲和性(Thread Affinity),它决定了:

  1. 对象属于哪个线程(创建它的线程)。

  2. 对象的信号槽、事件处理在哪个线程执行

1. 基本规则

(1)QObject 必须在创建它的线程中使用

  • 每个 QObject(及其子类,如 QTimerQWebSocket绑定到创建它的线程

  • 不能直接跨线程调用它的方法,否则可能导致崩溃或未定义行为。

(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 中有少量类是显式声明为线程安全的,例如:

  • QMutexQMutexLocker(线程同步)

  • QReadWriteLockQSemaphore

  • QAtomicIntQAtomicPointer(原子操作)

  • QWaitCondition

  • QThreadStorage

这些对象可以在任何线程直接使用,无需额外处理。

6.2非线程安全的 Qt 对象(如 QObject 及其子类)

大多数 Qt 对象(如 QWebSocketQTcpSocketQTimer)是 QObject 的子类,并且不是线程安全的。
它们的规则是:

  • 必须在创建它们的线程中使用(线程亲和性)。

  • 不能直接跨线程调用方法,否则可能导致崩溃或未定义行为。

 6.3如何判断一个 Qt 对象是否线程安全?

  • 查阅 Qt 官方文档,如果类文档明确说明它是 thread-safe,则可以跨线程使用。

  • 默认情况下,所有 QObject 子类都不是线程安全的,除非特别说明(如 QMutex)。

  • 基本数据类型(如 intQString)本身是线程安全的,但多线程访问时仍需同步(如用 QMutex)。

7. 总结

规则 说明
QObject 属于创建它的线程 不能直接跨线程调用方法
子对象继承父对象的线程 除非显式调用 moveToThread()
跨线程通信用信号槽或 invokeMethod 确保操作在正确线程执行
moveToThread() 必须在原线程调用 不能跨线程直接迁移对象

8. 完整示例:QWebSocket 跨线程通信(使用 moveToThread

以下是一个完整的跨线程 QWebSocket 示例,演示如何:

  1. 在主线程创建 QWebSocket

  2. 迁移到工作线程

  3. 通过信号槽安全通信

完整代码

(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);
}

关键点说明

  1. QWebSocket 只能在所属线程操作

    • 通过 moveToThread() 将其迁移到工作线程。

  2. 跨线程通信必须用信号槽或 invokeMethod

    • 主线程通过 QueuedConnection 调用 WebSocketManager 的方法。

  3. 线程安全退出

    • 使用 deleteLater 和 quit() + wait() 确保资源正确释放。


网站公告

今日签到

点亮在社区的每一天
去签到