(Qt) QThread 之 moveToThread

embedded/2025/2/28 10:41:45/

文章目录

    • 🧵前言
    • 🧵QObject::moveToThread
      • 🗒️Code
      • 🗒️moveToThread 的基础使用
      • 🗒️注意点
    • 🧵QThreadPool
      • 🗒️Code
      • 🗒️QThreadPool & QRunnable
      • 🗒️源码(接口)展示
    • ⭐END
      • 🌟交流方式

🧵前言

在之前的文章中,我们演示过 (Qt) QThread 信号槽所在线程 但是说得很潦草。

本文借助,QObject::moveToThread 来进行介绍一下。哎,这个函数不是在 QThread 中的(又是一个小细节)。

🧵QObject::moveToThread

🗒️Code

#include <QCoreApplication>
#include <QDebug>
#include <QObject>
#include <QThread>
#include <QTimer>/*** 本示例不考虑对象生命周期*/
void test_moveToThread() {/// 创建工作对象(不能指定父对象)QThread *thread = new QThread;QObject *worker = new QObject;qDebug() << "thread =" << thread;/// 将工作对象移动到新线程worker->moveToThread(thread);/// 启动线程,不启动的话工作对象的函数将永远无法触发在子线程thread->start();/*** 注意信号槽的 receiver* 会影响槽函数的执行线程*//// QTimer::singleShot 是默认在主线程操作的/// nullptr 默认执行在主线程QTimer::singleShot(100, nullptr, [worker, thread]() {qDebug() << "==============================";qDebug() << __LINE__ << QThread::currentThread() << QThread::currentThreadId();qDebug() << __LINE__ << "thread's" << thread->thread();qDebug() << __LINE__ << "work's" << worker->thread();});/// thread/// - 启动了,就可以跑在子线程了/// - 不启动,则不会执行这个回调QTimer::singleShot(1000, worker, [worker, thread]() {qDebug() << "==============================";qDebug() << __LINE__ << QThread::currentThread() << QThread::currentThreadId();qDebug() << __LINE__ << "thread's" << thread->thread();qDebug() << __LINE__ << "work's" << worker->thread();});
}int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);qDebug() << "main's" << QThread::currentThread() << QThread::currentThreadId();test_moveToThread();return app.exec();
}
main's QThread(0x10676b0) 0x1cf8
thread = QThread(0x106b280)
==============================
31 QThread(0x10676b0) 0x1cf8
32 thread's QThread(0x10676b0)
33 work's QThread(0x106b280)
==============================
41 QThread(0x106b280) 0x559c
42 thread's QThread(0x10676b0)
43 work's QThread(0x106b280)

🗒️moveToThread 的基础使用

使用规定:

  • 需要继承自 QObject
  • 创建对象时不能指定父对象,才能执行QObject::moveToThread()
  • 一般是配合信号槽的connect(..., receiver, member, ...)进行使用

最后一点非常关键,也是 connect 信号槽的执行原理

我们观察到两个QTimer::singleShot() 在执行回调的时候,打印的线程信息是不一样的。

  • QTimer::singleShot(100, nullptr, fun) 是在主线程执行的
    • 当 receiver,没有指定,或者说没有明确移动到一个 thread 中
    • 槽函数的回调将与 receiver 所在线程强相关
    • 如果是 nullptr 或者直接使用没有 receiver 有关,则与发送者 sender 有关
  • QTimer::singleShot(100, worker, fun) 是在子线程执行的
    • 因为这里的 worker 已经移动到一个已经启动 QThread::start() 的 QThread obj 中了
    • 如果移动的线程没有启动,则这个回调也是不会执行的。

🗒️注意点

一般一个 qobj 在不指明 moveToThread 的时候,默认跟随主线程的 thread,如直接查看worker->thread() 就是主线程的线程信息。

而无论怎么查看currentThread() 和 currentThreadId()都是决定于当时函数所处的环境。

因为有些人会用对象去进行调用,而这两个函数都是静态的。

如果没注意到这点,会有很大迷惑性,会误以为与 QObject::thread() 一致。

// 注意,这些都是静态函数
class Q_CORE_EXPORT QThread : public QObject {Q_OBJECT
public:static Qt::HANDLE currentThreadId() noexcept Q_DECL_PURE_FUNCTION;static QThread   *currentThread();static int        idealThreadCount() noexcept;static void       yieldCurrentThread();// pass
};

🧵QThreadPool

QThreadPool 不是本文重点。由于使用比较简单,这里顺便做点简单介绍。

🗒️Code

#include <QCoreApplication>
#include <QDebug>
#include <QRunnable>
#include <QThreadPool>/// 继承 QRunnable 才可以放入 QThreadPool
class MyTask : public QRunnable {
private:int m_id = -1;public:MyTask(int id) : m_id(id) {/// 设置任务完成后自动删除对象/// 默认就是 true/// 所以一般不允许同时放入线程池两次this->QRunnable::setAutoDelete(true);}~MyTask() {qDebug() << QString("[%1]").arg(m_id) << __func__;}public:/// 重写 QRunnable::run()void run() override {qDebug() << QString("Begin-Task[%1]").arg(m_id) << QThread::currentThreadId();QThread::sleep(2);qDebug() << QString("Finish-Task[%1]").arg(m_id) << QThread::currentThreadId();}
};int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);/// 获取全局线程池实例(全局单例)QThreadPool *threadPool = QThreadPool::globalInstance();qDebug() << "[maxThreadCount =" << threadPool->maxThreadCount();qDebug() << "[activeThreadCount =" << threadPool->activeThreadCount();const int taskCnt = qMax(1, threadPool->maxThreadCount() - 4);threadPool->setMaxThreadCount(taskCnt);qDebug() << "[maxThreadCount =" << threadPool->maxThreadCount();qDebug() << "[activeThreadCount =" << threadPool->activeThreadCount();for (int i = 0; i < taskCnt * 2; i += 1) {/// 创建继承自 QRunnable 的对象,并 start 到线程池中MyTask *task = new MyTask(i);threadPool->start(task);}/// [阻塞]/// 等待所有任务完成threadPool->waitForDone();qDebug() << "All tasks are finished.";return app.exec();
}
[maxThreadCount = 8
[activeThreadCount = 0
[maxThreadCount = 4
[activeThreadCount = 0
"Begin-Task[3]" 0x292c
"Begin-Task[2]" 0x4938
"Begin-Task[0]" 0x1610
"Begin-Task[1]" 0x5cbc
"Finish-Task[0]" 0x1610
"[0]" ~MyTask
"Finish-Task[3]" 0x292c
"Finish-Task[2]" 0x4938
"Finish-Task[1]" 0x5cbc
"Begin-Task[4]" 0x1610
"[2]" ~MyTask
"Begin-Task[5]" 0x4938
"[3]" ~MyTask
"Begin-Task[6]" 0x292c
"[1]" ~MyTask
"Begin-Task[7]" 0x5cbc
"Finish-Task[5]" 0x4938
"[5]" ~MyTask
"Finish-Task[4]" 0x1610
"Finish-Task[6]" 0x292c
"Finish-Task[7]" 0x5cbc
"[6]" ~MyTask
"[7]" ~MyTask
"[4]" ~MyTask
All tasks are finished.

🗒️QThreadPool & QRunnable

QThreadPool::globalInstance() 具有一个全局的单例,可以直接获取。

需要一个继承了 QRunnableoverrideQRunnable::run() 的实例,即可作用于线程池。

注意 QRunnable obj默认是会在执行完后自动删除的,即QRunnable::setAutoDelete(true)所以需要注意生命周期。

🗒️源码(接口)展示

由于两者的源码的接口比较清晰,这里就顺便展示一下。version: Qt 5.14

注意:QRunnable 是没有继承自 QObject 的。

// Qt 5.14class Q_CORE_EXPORT QRunnable {int ref;friend class QThreadPool;friend class QThreadPoolPrivate;friend class QThreadPoolThread;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)Q_DISABLE_COPY(QRunnable)
#endif
public:virtual void run() = 0;QRunnable() : ref(0) {}virtual ~QRunnable();bool autoDelete() const {return ref != -1;}void setAutoDelete(bool _autoDelete) {ref = _autoDelete ? 0 : -1;}
};class Q_CORE_EXPORT QThreadPool : public QObject {Q_OBJECTQ_DECLARE_PRIVATE(QThreadPool)Q_PROPERTY(int expiryTimeout READ expiryTimeout WRITE setExpiryTimeout)Q_PROPERTY(int maxThreadCount READ maxThreadCount WRITE setMaxThreadCount)Q_PROPERTY(int activeThreadCount READ activeThreadCount)Q_PROPERTY(uint stackSize READ stackSize WRITE setStackSize)friend class QFutureInterfaceBase;public:QThreadPool(QObject *parent = nullptr);~QThreadPool();static QThreadPool *globalInstance();void start(QRunnable *runnable, int priority = 0);bool tryStart(QRunnable *runnable);int  expiryTimeout() const;void setExpiryTimeout(int expiryTimeout);int  maxThreadCount() const;void setMaxThreadCount(int maxThreadCount);int activeThreadCount() const;void setStackSize(uint stackSize);uint stackSize() const;void reserveThread();void releaseThread();bool waitForDone(int msecs = -1);void clear();#if QT_DEPRECATED_SINCE(5, 9)QT_DEPRECATED_X("use tryTake(), but note the different deletion rules")void cancel(QRunnable *runnable);
#endifQ_REQUIRED_RESULT bool tryTake(QRunnable *runnable);
};

⭐END

🌟交流方式

关注我,学习更多C/C++,python,算法,软件工程,计算机知识!

⭐交流方式⭐ |C/C++|算法|设计模式|软件架构-CSDN社区

B站

👨‍💻主页:天赐细莲 bilibili


http://www.ppmy.cn/embedded/167782.html

相关文章

【全栈开发】从0开始搭建一个图书管理系统【一】框架搭建

【全栈开发】从0开始搭建一个图书管理系统【一】框架搭建 前言 现在流行降本增笑&#xff0c;也就是不但每个人都要有事干不能闲着&#xff0c;更重要的是每个人都要通过报功的方式做到平日的各项工作异常饱和&#xff0c;实现1.5人的支出干2人的活计。单纯的数据库开发【肤浅…

全面解析:如何查找电脑的局域网与公网IP地址‌

在数字化时代&#xff0c;IP地址作为网络设备的唯一标识&#xff0c;对于网络连接、远程访问、网络诊断等方面都至关重要。无论是出于工作需要&#xff0c;还是解决网络问题&#xff0c;了解怎么查找电脑的IP地址都是一项必备技能。本文将详细介绍几种常见的方法&#xff0c;帮…

Vue nextTick原理回顾

nextTick就是将异步函数放在下一次实践循环的微任务队列中执行 实现原理比较简单&#xff0c;极简版本&#xff1a; function myNextTick(cb){let p;pPromise.resolve().then(cb)return cb?p:Promise.resolve() }复杂版本&#xff0c;考虑异步函数入队、执行锁、兼容处理 l…

Ubuntu 20.04 安装 Node.js 20.x、npm、cnpm 和 pnpm 完整指南

&#x1f310; Ubuntu 20.04 安装 Node.js 20.x、npm、cnpm 和 pnpm 完整指南 &#x1f680; 在本文中&#xff0c;我们将介绍如何在 Ubuntu 20.04 上安装 Node.js 20.x&#xff0c;以及如何安装 npm、cnpm 和 pnpm 来提高开发效率 ⚡。 1️⃣ 安装 Node.js 20.x 为了确保使用…

Vue组件间通信的方式

组件间通信的分类&#xff1a; 父子组件之间的通信兄弟组件之间的通信祖孙与后代组件之间的通信非关系组件间之间的通信 组件间通信的方案&#xff1a; 通过 props 传递通过 $emit 触发自定义事件使用 refEventBus通过 $parent 或 $rootattrs 与listenersProvide 与 InjectV…

机器人学习模拟框架 robosuite 支持强化学习和模仿学习 (1) 快速入门

RoboSuite 是一款基于MuJoCo物理引擎构建的机器人学习模拟框架。 在现有版本&#xff08;v1.5&#xff09;中&#xff0c;它涵盖了丰富多样的机器人实例支持&#xff0c;诸如人形机器人、自定义机器人组合&#xff0c;还包含复合控制器&#xff08;像全身控制器等&#xff09;…

大语言加持的闭环端到端自动驾驶模型 学习笔记纯干货

LMDrive&#xff1a;大语言模型辅助闭环端到端 LMDrive&#xff1a;大语言模型辅助闭环端到端 背景框架输入部分&#xff1a;导航指令&#xff1a;视觉数据&#xff1a;提示指令&#xff08;可选&#xff09;&#xff1a;处理部分&#xff1a;输出部分&#xff1a; 视觉编码器…

leetcode 169. 多数元素

题目如下 显然当多数元素存在时我们可以先排序此时多数元素就组成了一个长度超过一半的"滑动窗口"。 那么无论我们怎么样滑动这个窗口显然数组的中心点必然落在这个窗口内&#xff0c;即排序后的中间元素就是多数元素。法一代码 class Solution { public:int majo…