当前位置: 首页 > news >正文

Qt 的对象线程亲和性规则

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);// 布局代码略...// 创建工作线程和 WebSocketManagerm_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() 确保资源正确释放。

http://www.lryc.cn/news/615127.html

相关文章:

  • 华为欧拉OpenEnler系统在启动MindIE时权限问题的解决方法
  • 从灵感枯竭到批量产出:无忧秘书创作平台如何重构内容生产者的工作流程?全环节赋能分析
  • Spring Boot 集成 Quartz 实现定时任务(Cron 表达式示例)
  • WinForm 中 ListView 控件的实战应用与功能拓展
  • kafka架构原理快速入门
  • AI大语言模型在生活场景中的应用日益广泛,主要包括四大类需求:文本处理、信息获取、决策支持和创意生成。
  • 软件定义车辆加速推进汽车电子技术
  • Blender 快捷键速查表 (Cheat Sheet)
  • 【线性代数】6二次型
  • 可直接运行的 Playwright C# 自动化模板
  • 通过 Certimate 统一管理 SSL 证书 支持自动化申请、全平台部署
  • 【线性代数】线性方程组与矩阵——(1)线性方程组与矩阵初步
  • 数据挖掘2.6 Perceptron Modeling 感知器建模
  • 我想做自动化报社保,用哪种技术更好一点呢?
  • stm32项目(25)——基于stm32的植物生长箱环境监测系统
  • 「iOS」————响应者链与事件传递链
  • GPT-5:数字大脑的进化史
  • 人工智能-python-数据处理实战-特征降维(PCA)
  • CD63.【C++ Dev】多态(2): 剖析虚函数表的前置知识
  • 【线性代数】线性方程组与矩阵——(3)线性方程组解的结构
  • 【CTF】PHP反序列化基础知识与解题步骤
  • 华为实验:SSH
  • 华为实验: 单区域/多区域OSPF
  • [优选算法专题一双指针——四数之和]
  • 【Leecode 随笔】
  • 大模型在垂直场景的创新应用:搜索、推荐、营销与客服新玩法
  • Q-learning强化算法万字详解
  • 关于C语言本质的一些思考
  • Python(6) -- 数据容器
  • Python映射合并技术:多源数据集成的高级策略与工程实践