文章目录
- 🧵前言
- 🧵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 中了 - 如果移动的线程没有启动,则这个回调也是不会执行的。
- 因为这里的 worker 已经移动到一个已经启动
🗒️注意点
一般一个 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()
具有一个全局的单例,可以直接获取。
需要一个继承了 QRunnable
且 override
了 QRunnable::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
![]()