西安网站开发公司疫情最新动态
引用
Qt多线程中使用QTimer(常见问题汇总)
[多线程]多线程使用QTimer
Qt::ConnectionType:Qt不同类型connect的详细区别说明与应用
Qt的4种多线程实现方式
一文搞定之Qt多线程(QThread、moveToThread)
QTimer
The QTimer class provides repetitive and single-shot timers.
The QTimer class provides a high-level programming interface for timers. To use it, create a QTimer, connect its timeout() signal to the appropriate slots, and call start(). From then on, it will emit the timeout() signal at constant intervals.
使用QTimer类定义一个定时器,它可以不停重复,也可以只进行一次便停止
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000);
创建一个QTimer对象,将信号timeout()
与相应的槽函数相连,然后调用start()
函数。接下来,每隔一段时间,定时器便会发出一次timeout()信号
Qt多线程的实现
QThread::run()
QThread 是 Qt 中实现多线程的基础类,通过继承 QThread 类并重写其 run() 函数可以实现自定义线程逻辑
- 线程类
#ifndef WORKER_H
#define WORKER_H#include <QThread>class Worker : public QThread
{
public:Worker();void run();void printFunc();};#endif // WORKER_H
#include "Worker.h"
#include <QDebug>Worker::Worker()
{}void Worker::run()
{qDebug()<<"子线程ThreadID: "<<QThread::currentThreadId();
}void Worker::printFunc()
{qDebug()<<"子线程成员函数ThreadID: "<<QThread::currentThreadId();
}
- main函数
#include <iostream>
#include <QDebug>
#include "Worker.h"using namespace std;int main()
{Worker w;w.start();qDebug()<<"主线程ThreadID: "<<QThread::currentThreadId();w.printFunc();while (1){}return 0;
}
- 执行结果
子线程ThreadID: 0x4138
主线程ThreadID: 0x34b0
子线程成员函数ThreadID: 0x34b0
主线程和子线程执行的顺序不确定,偶尔主线程在前,偶尔子线程在前
子线程类的成员函数包括槽函数是运行在主线程当中的,只有run()函数运行在子线程中
如果在run()函数中调用子线程类成员函数,那么该成员函数运行在子线程中
- 在run()函数中调用子线程成员函数
#include "Worker.h"
#include <QDebug>Worker::Worker()
{}void Worker::run()
{qDebug()<<"子线程ThreadID: "<<QThread::currentThreadId();printFunc();
}void Worker::printFunc()
{qDebug()<<"子线程成员函数ThreadID: "<<QThread::currentThreadId();
// emit doTask();
}
主线程ThreadID: 0x2140
子线程ThreadID: 0x2044
子线程成员函数ThreadID: 0x2044
QThread::moveToThread()
moveToThread() 是 Qt 中用于将对象移动到另一个线程的方法。通过调用 moveToThread() 函数,可以将一个 QObject 对象从当前线程移动到另一个线程中,从而实现对象在新线程中执行特定的任务
在多线程编程中,通常会使用 moveToThread() 方法来将耗时的任务或需要在单独线程中执行的逻辑移动到单独的线程中,以避免阻塞主线程(通常是 GUI 线程)的执行
- 线程类
#ifndef WORKER_H
#define WORKER_H#include <QObject>class Worker : public QObject
{Q_OBJECT
public:Worker();void printFunc();public slots:void doWork();void doWork2();void doWork3();signals:void testdoWork3();};#endif // WORKER_H
#include "Worker.h"
#include <QDebug>
#include <QThread>Worker::Worker()
{}void Worker::printFunc()
{qDebug() << "成员函数ThreadID:"<<QThread::currentThreadId();}void Worker::doWork()
{qDebug() << "doWork ThreadID:"<<QThread::currentThreadId();
}void Worker::doWork2()
{qDebug() << "doWork2 ThreadID:"<<QThread::currentThreadId();
}void Worker::doWork3()
{qDebug() << "doWork3 ThreadID:"<<QThread::currentThreadId();
}
- main函数
#include "mainwindow.h"#include <QApplication>
#include "Worker.h"
#include <QDebug>
#include <QThread>int main(int argc, char *argv[]) {QApplication a(argc, argv);//MainWindow w;//w.show();Worker worker;QThread thread;worker.moveToThread(&thread);QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork); //第一槽函数QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork2); //第二槽函数QObject::connect(&worker, &Worker::testdoWork3, &worker, &Worker::doWork3); //第三槽函数//启动线程thread.start();//调用成员数worker.printFunc();//发送自定义信号emit worker.testdoWork3();while (1) {}return a.exec();
}
- 执行结果
成员函数ThreadID: 0x1330
doWork ThreadID: 0x4070
doWork2 ThreadID: 0x4070
doWork3 ThreadID: 0x4070
槽函数无论是线程的信号触发还是自定义信号触发,槽函数都在新线程里
运行
成员函数和主函数运行在主线程当中
QtConcurrent::run()
QtConcurrent::run
是 Qt 库中的一个用于并行执行任务的函数。它允许你在一个独立的线程中运行一个函数,而无需显式地创建和管理线程。这是通过将函数和其参数传递给 QtConcurrent::run
来实现的,Qt 会自动管理线程池和线程的生命周期
QtConcurrent::run能够方便快捷的将任务丢到子线程中去执行,无需继承任何类,也不需要重写函数,使用非常简单。需要注意的是,由于该线程取自全局线程池QThreadPool,函数不能立马执行
,需要等待线程可用时才会运行
使用QtConcurrent::run()的基本步骤:
- 传递一个函数或可调用对象(例如 lambda、函数指针、成员函数等)给
QtConcurrent::run
QtConcurrent::run
会在后台线程中执行该函数,并返回一个QFuture
对象,表示异步操作的结果- 可以通过
QFuture
来监控任务的进度和结果
基本语法
QFuture<TResult> QtConcurrent::run(Func func, Args... args);
func
:要执行的函数或可调用对象args
:传递给函数的参数
#include <QCoreApplication>
#include <QtConcurrent>
#include <QDebug>
#include <QThread>
#include <QFuture>void slowFunction(int seconds) {qDebug() << "Start slow function in thread:" << QThread::currentThreadId();QThread::sleep(seconds); // 模拟耗时操作qDebug() << "Finished slow function";
}int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);// 在后台线程执行 slowFunctionQFuture<void> future = QtConcurrent::run(slowFunction, 5); // 传入参数 5 表示等待 5 秒// 在此可以执行其他操作,不会被阻塞qDebug() << "Main thread doing something else...";// 等待后台任务完成future.waitForFinished();return a.exec();
}
QtConcurrent::run(slowFunction, 5)
会在后台线程中运行slowFunction
,并传递参数5
,该函数会使线程等待 5 秒钟- 在此过程中,主线程可以继续执行其他任务,比如打印 “Main thread doing something else…”。
future.waitForFinished()
会阻塞主线程直到后台任务完成
如果任务有返回值,可以通过 QFuture::result()
获取
#include <QCoreApplication>
#include <QtConcurrent>
#include <QDebug>
#include <QFuture>int computeSum(int a, int b) {qDebug() << "Computing sum in background thread...";QThread::sleep(2); // 模拟耗时操作return a + b;
}int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);// 在后台线程中执行带有返回值的任务QFuture<int> future = QtConcurrent::run(computeSum, 5, 10);// 在此可以继续执行其他任务qDebug() << "Main thread is free to do other work";// 获取任务的结果int result = future.result(); // 阻塞直到任务完成qDebug() << "The result of computation is:" << result;return a.exec();
}
如果需要在任务完成后处理结果,可以使用 QFuture::watch()
或者 QFutureWatcher
来接收通知
QtConcurrent::run
是一个简单的 API,它依赖于 Qt 的线程池来管理线程,因此它适合处理不需要显式控制线程的简单任务
总结
moveToThread对比传统子类化Qthread更灵活
,仅需要把你想要执行的代码放到槽,movetothread这个object到线程,然后拿一个信号连接到这个槽就可以让这个槽函数在线程里执行。可以说,movetothread给我们编写代码提供了新的思路,当然不是说子类化QThread不好,只是你应该知道还有这种方式去调用线程
轻量级的函数可以用movethread,多个短小精悍能返回快速的线程函数适用 ,无需创建独立线程类,例如你有20个小函数要在线程内做, 全部扔给一个QThread。而我觉得movetothread和子类化QThread的区别不大,更可能是使用习惯引导。
又或者你一开始没使用线程,但是后边发觉这些代码还是放线程比较好,如果用子类化QThread的方法重新设计代码,将会有可能让你把这一段推到重来,这个时候,moveThread的好处就来了,你可以把这段代码的从属着movetothread,把代码移到槽函数,用信号触发它就行了
。其它的话movetothread它的效果和子类化QThread的效果是一样的,槽就相当于你的run()函数,你往run()里塞什么代码,就可以往槽里塞什么代码,子类化QThread的线程只可以有一个入口就是run(),而movetothread就有很多触发的入口
以下是一些常用的QThread函数:
- start(): 启动线程,使线程进入运行状态,调用线程的run()方法
- run(): 线程的执行函数,需要在该函数中编写线程所需执行的任务
- quit(): 终止线程的事件循环,在下一个事件处理周期结束时退出线程
- wait(): 阻塞当前线程,直到线程执行完成或超时
- finished(): 在线程执行完成时发出信号
- terminate(): 强制终止线程的执行,不推荐使用,可能导致资源泄漏和未定义行为
- isRunning(): 判断线程是否正在运行
- currentThreadId(): 返回当前线程的ID
- yieldCurrentThread(): 释放当前线程的时间片,允许其他线程执行
- setPriority(): 设置线程优先级
- msleep(): 让当前线程休眠指定的毫秒数
函数使用时的注意事项
- start()函数: 调用start()函数启动线程时,会自动调用线程对象的run()方法。
不要直接调用run()方法来启动线程,应该使用start()函数
- wait()函数: wait()函数会阻塞当前线程,直到线程执行完成。在调用wait()函数时
需要确保不会发生死锁
的情况,避免主线程和子线程相互等待对方执行完成而无法继续 - terminate()函数: 调用terminate()函数会强制终止线程,这样可能会
导致资源未能正确释放
,造成内存泄漏等问题。因此应该尽量避免使用terminate()函数,而是通过设置标志让线程自行退出 - quit()函数: quit()函数用于终止线程的事件循环,通常
与exec()函数一起使用
。在需要结束线程事件循环时,可以调用quit()函数 - finished信号: 当线程执行完成时会发出finished信号,可以连接这个信号来处理线程执行完成后的操作
- yieldCurrentThread()函数: yieldCurrentThread()函数用于让当前线程让出时间片,让其他线程有机会执行。使用时应该注意避免过多的调用,否则会影响程序性能
多线程中的QTimer
错误用法
错误用法1
子类化QThread,在线程类中定义一个定时器,然后在run()方法中调用定时器的start()方法
TestThread::TestThread(QObject *parent): QThread(parent)
{m_pTimer = new QTimer(this);connect(m_pTimer, &QTimer::timeout, this, &TestThread::timeoutSlot);
}void TestThread::run()
{m_pTimer->start(1000);
}void TestThread::timeoutSlot()
{qDebug() << QString::fromLocal8Bit("当前线程id:") << QThread::currentThread();
}
接下来在主线程中创建该线程对象,并调用它的start()方法
m_pThread = new TestThread(this);
m_pThread->start();
执行时会报错,定时器不能被其它线程start
QObject::startTimer: Timers cannot be started from another thread
分析一下:
- 刚开始只有主线程一个,TestThread的实例是在主线程中创建的,定时器在TestThread的构造函数中,所以也是在主线程中创建的
- 当调用TestThread的start()方法时,这时有两个线程。定时器的start()方法是在另一个线程中,也就是TestThread中调用的
💡 创建和调用并不是在同一线程中,所以出现了错误
每个QObject实例都有一个叫做“线程关系”(thread affinity)的属性,或者说,它处于某个线程中。默认情况下,QObject处于创建它的线程中。
当QObject接收队列信号(queued signal)或者传来的事件(posted event),槽函数或事件处理器将在对象所处的线程中执行。
根据以上的原理,Qt使用计时器的线程关系(thread affinity)来决定由哪个线程发出timeout()信号。正因如此,你必须在它所处的线程中start或stop该定时器,在其它线程中启动定时器是不可能的。
错误用法2
OK,看完错误1,那是不是在run函数里面构造定时器就可以了,构造也要小心喔,别一不小心指定了父类,如下
void MyThread::run()
{timer = new QTimer(this);connect(timer,SIGNAL(timeout()),this,SLOT(timerOut()));timer->start(1000);exec();
}
执行时报错
QObject: Cannot create children for a parent that is in a different thread.
(Parent is TestThread(0x709d88), parent’s thread is QThread(0x6e8be8), current thread is TestThread(0x709d88)
因为TestThread对象是在主线程中创建的,它的QObject子对象也必须在主线程中创建。所以不能指定父对象为TestThread
错误用法3
这个问题也很容易犯,即计时器虽在线程中初始化,但在子线程非run()函数中开始start()
void MyThread::run()
{timer = new QTimer();connect(timer,SIGNAL(timeout()),this,SLOT(timerOut()));startTimer();exec();
}void MyThread::startTimer(){qDebug()<<"当前线程ID:"<<QThread::currentThreadId();timer->start(1000);
}void MyThread::timerOut()
{qDebug()<<"当前线程ID:"<<QThread::currentThreadId();
}
执行输出
前线程ID: 0x4338
当前线程ID: 0x3e20
当前线程ID: 0x3e20
run()函数线程为子线程,因此run函数中运行startTimer()为run所处线程,但因为startTimer()初始化时为主线程,因此timer->start()也随之在主线程中开始,计时器槽函数也在主线程中运行
正确用法
正确用法1
void MyThread::run()
{timer = new QTimer();connect(timer,SIGNAL(timeout()),this,SLOT(timerOut()));timer->start(1000);exec();
}void MyThread::timerOut()
{qDebug()<<"当前线程ID:"<<QThread::currentThreadId();
}
执行输出
主线程ID: 0x4458
当前线程ID: 0x4094
当前线程ID: 0x4094
当前线程ID: 0x4094
正确用法2
无需子类化线程类,通过信号启动定时器
TestClass::TestClass(QWidget *parent): QWidget(parent)
{m_pThread = new QThread(this);m_pTimer = new QTimer();m_pTimer->moveToThread(m_pThread);m_pTimer->setInterval(1000);connect(m_pThread, SIGNAL(started()), m_pTimer, SLOT(start()));connect(m_pTimer, &QTimer::timeout, this, &ThreadTest::timeOutSlot, Qt::DirectConnection);
}
通过moveToThread()方法改变定时器所处的线程,不要给定时器设置父类
,否则该函数将不会生效
首先将定时器所处的线程改为新建的线程,然后连接信号槽,槽函数在定时器所处的线程中执行,注意信号槽的连接类型为Qt::DirectConnection
正确用法3
如果不想在run()函数中就开始计时器,而是在线程运行过程中根据实际情况开启(之前错误3的情况)时要怎么办呢
解决办法为:信号与槽连接中使用Qt::*DirectConnection*
信号与槽连接默认方式为:Qt::AutoConnection
,因此会根据函数所处的线程自动跳转
,导致错误3中的情况
void MyThread::run()
{timer = new QTimer();connect(timer,SIGNAL(timeout()),this,SLOT(timerOut()),Qt::DirectConnection);startTimer();exec();
}void MyThread::startTimer(){qDebug()<<"当前线程ID:"<<QThread::currentThreadId();timer->start(1000);
}void MyThread::timerOut()
{qDebug()<<"当前线程ID:"<<QThread::currentThreadId();
}
执行输出
主线程ID: 0x4424
当前线程ID: 0x470c
当前线程ID: 0x470c
当前线程ID: 0x470c
当前线程ID: 0x470c
Qt::ConnectionType
从上面的内容可以看到又引申出了多线程中槽函数在哪个线程中执行的问题,这里再补充说明下
同一对象的不同槽函数可以有不同的连接类型,槽函数在哪个线程被执行由连接类型、信号发射时收发双方是否在一个线程决定
类型说明
Qt::AutoConnection
默认连接类型,如果信号接收方与发送方在同一个线程,则使用Qt::DirectConnection,否则使用Qt::QueuedConnection;连接类型在信号发射时决定
Qt::DirectConnection
信号所连接至的槽函数将会被立即执行,并且是在发射信号的线程;倘若槽函数执行的是耗时操作、信号由UI线程发射,则会阻塞Qt的事件循环,UI会进入无响应状态
Qt::QueuedConnection
槽函数将会在接收者的线程被执行,此种连接类型下的信号倘若被多次触发、相应的槽函数会在接收者的线程里被顺次执行相应次数;当使用QueuedConnection时,参数类型必须是Qt基本类型,或者使用qRegisterMetaType() 进行注册了的自定义类型
Qt::BlockingQueuedConnection
和Qt::QueuedConnection类似,区别在于发送信号的线程在槽函数执行完毕之前一直处于阻塞状态;收发双方必须不在同一线程,否则会导致死锁
Qt::UniqueConnection
可以搭配以上所有连接类型使用,一经设置之后,同一信号与同一槽函数的二次连接将会失败
测试验证
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QThread>
#include <QPushButton>
#include <windows.h>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass ThreadWorker : public QObject
{Q_OBJECT
public:ThreadWorker(QObject* parent);public slots:void queuedConnect();void directConnect();
};class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private:QVector<QThread*> listThread;QVector<ThreadWorker*> listWorker;Ui::Widget *ui;private slots:void on_butDirect_clicked();void on_butQueued_clicked();void on_butAuto_clicked();signals:void directConnect();void autoConnect();void queuedConnect();};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);for(int i = 0 ; i < 3 ; i++){listThread.append(new QThread(this));listWorker.append(new ThreadWorker(nullptr));}connect(this,SIGNAL(directConnect()),listWorker[0],SLOT(directConnect()),Qt::DirectConnection);connect(this,SIGNAL(autoConnect()),listWorker[2],SLOT(directConnect()),Qt::AutoConnection);connect(this,SIGNAL(queuedConnect()),listWorker[1],SLOT(queuedConnect()),Qt::QueuedConnection);connect(this,SIGNAL(directConnect()),listWorker[1],SLOT(directConnect()),Qt::QueuedConnection);for(int i = 0 ; i < 3 ; i++ )listWorker[i]->moveToThread(listThread[i]);for(auto i : listThread) i->start();qDebug()<<"The main thread id is "<< QThread::currentThreadId();}Widget::~Widget()
{delete ui;for(auto i : listThread)i->exit();
}void Widget::on_butDirect_clicked()
{emit directConnect();
}void Widget::on_butQueued_clicked()
{emit queuedConnect();
}void Widget::on_butAuto_clicked()
{emit autoConnect();}ThreadWorker::ThreadWorker(QObject *parent) : QObject(parent)
{}void ThreadWorker::queuedConnect()
{for(int i = 0 ; i <10 ; i ++)Sleep(100);qDebug()<<"The kid thread id is "<< QThread::currentThreadId();}
void ThreadWorker::directConnect()
{for(int i = 0 ; i <10 ; i ++)Sleep(100);qDebug()<<"The kid thread id is "<< QThread::currentThreadId();}
执行输出
The main thread id is 0x30b0
The kid thread id is 0x30b0
The kid thread id is 0x7cc
The kid thread id is 0x7cc
The kid thread id is 0x24c0
小结
这里参考学习了多篇博客,对Qt的多线程实现,以及如何在多线程场景中使用QTimer有了更深的认识,同时对多线程中槽函数的调用也有了新的认识。希望和大家一起进步