Qt多线程用法汇总

对于界面开发而言,多线程一个非常重要的作用就是将复杂的运算处理分开执行,避免造成界面的卡顿和冻结,甚至被系统强制关闭。另外,使用多线程编程可以最大限度地调用CPU资源,尤其对于多处理器系统。本文基于Visual Studio 2015、Qt5.6.3的项目,整理了多种Qt多线程技术的实例和介绍,并简单归纳了使用场景和注意事项。

1 QThread

继承QThread,重载run函数。

QThread是Qt提供的类,比较适用于处理耗时很长的业务。要创建一个新的线程,可以定义一个MyThread类继承自QThread,然后重新实现QThread::run()。我们可以通过信号和槽机制来通知主线程,比如线程结束消息。
示例代码:
.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    ~MyThread();

protected:
    void run();

signals:
    /* 处理完成信号 */
    void isDone();
};

#endif // MYTHREAD_H

.cpp

#include "MyThread.h"

MyThread::MyThread(QObject *parent)
{
}
MyThread::~MyThread()
{
}
void MyThread::run()
{
    /* 处理耗时操作 */
    sleep(5);

    /* 发送完成信号 */
    emit isDone();
}

使用方法:
使用start()来启动线程

MyThread *pThread = new MyThread(this);
connect(pThread , &MyThread::isDone, this, &MyWidget::slotDone);
connect(pThread , &MyThread::finished, pThread , &QObject::deleteLater);

使用quit()、wait()来关闭线程

/* 停止线程 */
 thread->quit();

/* 等待线程处理完手头工作 */
 thread->wait();

注意事项:
1)run()函数在新线程中执行,run()函数执行结束,线程结束。
2)MyThread实例化的对象属于创建它的线程,而不是run()函数所在线程。
3)MyThread没有事件循环,除非在run()函数中调用exec();
4)队列连接到MyThread的slotDone()函数,slotDone()函数在创建MyThread对象的线程中执行。
5)直接调用MyThread的方法,该方法的执行线程为调用处的线程。

2 QObject

继承QObject,调用void QObject::moveToThread(QThread *targetThread)。

这种方法适用于在一个类中处理多个耗时任务,且这个些任务不会并行执行的情况。
示例代码:

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>

class MyThread : public QObject
{
    Q_OBJECT
        signals:
    /* 处理完成信号 */
    void isDone();

public slots:
    void doWork()
    {
        /* TODO:处理耗时操作 */

        /* 发送完成信号 */
        emit isDone();
    }
};

#endif // MYTHREAD_H

使用方法:
创建一个Qthread子线程对象,把我们的自定义线程类,加入到子线程;启动子线程,只是把线程开启了,并没有启动线程处理函数。

QThread *pThread = new QThread(this);
MyThread *pMyThread= new MyThread ;
pMyThread->moveToThread(pThread);
connect(pThread , &QThread::finished, pMyThread, &QObject::deleteLater);
connect(this, &MyWidget::StartWork, pMyThread, &MyThread::doWork);
connect(pMyThread, &MyThread::isDone, this, &MyWidget::slotDone);
pThread.start();

通过关闭子线程来关闭自定义线程类

pThread.quit();
pThread.wait();

注意事项:
1)调用moveToThread函数的对象不能设置父对象。
2)MyThread类中的槽函数可以跟任意线程的任意信号建立连接,队列连接时,在新线程中执行。
3)直接调用MyThread类中的函数,在调用线程内执行。
4)同时发送多个与MyThread类中槽函数连接的信号,槽函数依次执行。

3 QThreadPool

频繁创建和销毁线程会带来较大的性能开销,影响程序执行效率。使用QThreadPool可以有效解决这个问题。QThreadPool搭配QRunnable使用非常简单,如下所示。
示例代码:

 class MyThread: public QRunnable
  {
      void run() override
      {
          qDebug() << "Test MyThread" << QThread::currentThread();
      }
  };
 
  MyThread* pThread= new MyThread();

  /* QThreadPool会自动销毁pThread */
  QThreadPool::globalInstance()->start(pThread);

注意事项:
1)默认情况下,run()函数执行完,pThread对象会被线程池自动删除。可以使用setAutoDelete函数设置。
2)QThreadPool::start()多次启动设置为autoDelete的QRunnable对象,可能导致崩溃。

4 QtConcurrent

QtConcurrent提供了高级api,使编写多线程程序时,不需要使用诸如互斥锁、读写锁、等待条件或信号量等低级线程安全类。

4.1 Run()函数

QtCuncurrent::run()表示在一个单独的线程中执行函数,它的基本原型如下:

QFuture QtConcurrent::run(QThreadPool *pool, Function function, …)

参数 pool :线程池。表示从线程池中获取一个线程来执行该函数。
参数 function: 表示要在线程中执行的函数。
返回值 :返回一个 QFuture<T>对象。

实际使用时,常用到QFuture QtConcurrent::run(Function function, …),相当于QtConcurrent::run(QThreadPool::globalInstance(), function, …),表示从全局的线程池中,获取线程。

示例代码:

extern void testFunction(int arg1, double arg2, const QString &arg3);

int iValue = 0;
double dValue = 0;
QString strValue;

QFuture<void> future = QtConcurrent::run(testFunction, iValue, dValue, strValue);

在调用run()的时候,这些值会被传递给执行函数,之后修改这些变量的值, 比如重新设置 iValue的值,并不会对计算产生影响。
另外,function可以为普通函数、类的成员函数、仿函数、lambda表达式,
示例代码:
(1)调用const的成员函数

// call 'QList<QByteArray>  QByteArray::split(char sep) const' in a separate thread
QByteArray bytearray = "hello world";
QFuture<QList<QByteArray> > future = QtConcurrent::run(bytearray, &QByteArray::split, ',');
...
QList<QByteArray> result = future.result();

(2)调用non-const成员函数

// call 'void QImage::invertPixels(InvertMode mode)' in a separate thread
QImage image = ...;
QFuture<void> future = QtConcurrent::run(&image, &QImage::invertPixels, QImage::InvertRgba);
...
future.waitForFinished();
// At this point, the pixels in 'image' have been inverted

(3)使用lambda表达式:

QFuture<void> future = QtConcurrent::run([=]() {
      // Code in this block will run in another thread
  });
  ...

注意事项:
1)run里面的函数可能不会立即执行;一旦线程池中的线程有可用的线程时,才会被执行。

4.2 QFuture

使用QFuture可以获取和控制当前计算的状态并获取计算完毕之后的结果。
示例代码:

extern int func(void);
int func(void)
{
	/* do some thing */
}

QFuture<int> future = QtConcurrent::run(func);

  /* 该函数会阻塞等待线程执行func的计算结果返回 */
int result = future.result();

另外,QFuture的常用接口有下面这些:

(1)获取结果相关函数
result()函数会判断结果是否为可用的,如果不可用则阻塞等待。如果可用则直接把结果返回。

(2)设置运行状态函数
cancel() 取消,pause() 暂停,Resumes() 恢复,这些函数在run() 这种模式下无效。

(3)获取运行状态函数
isCanceled() 、 isStarted()、 isFinished()、isRunning()和isPaused()。

(4)进度信息
progressValue()、progressMinimum()、progressMaximum()和progressText() ,在run() 模式下,这些值并没有正真的意义。waitForFinished() 会阻塞等待计算的完成,直到结果为可用的状态。

注意事项:
(1)QFuture<T> 模板 T 这个类型,需要有默认构造和拷贝构造。
(2)QFuture是一个基于引用计数的轻量级的类。

4.3 QFutureWatcher

我们通常想要通过信号和槽的函数,异步的监控QFuture状态信息。这就需要 QFutureWatcher这个类。
示例代码:

// Instantiate the objects and connect to the finished signal.
MyClass myObject;
QFutureWatcher<int> watcher;
connect(&watcher, SIGNAL(finished()), &myObject, SLOT(handleFinished()));

// Start the computation.
QFuture<int> future = QtConcurrent::run(...);
watcher.setFuture(future);

上述代码中,用setFuture设置被监控的QFuture对象,为收到QFuture对象发送的信号,需要在执行run()之前设置信号和槽的连接。当QFuture对象的结果可用的时候,QFutureWatcher会发送finished信号 。

5 使用场景

下面这张表简单整理了上面介绍的4种Qt多线程方法及其应用场景,

生命周期开发任务解决方案
一次调用在另一个线程中运行一个函数,函数完成时退出线程(1)编写函数,使用QtConcurrent::run运行它;(2)派生QRunnable,使用QThreadPool::globalInstance()->start() 运行它;(3)派生QThread,重新实现QThread::run() ,使用QThread::start() 运行它
一次调用需要操作一个容器中所有的项。使用处理器所有可用的核心。一个常见的例子是从图像列表生成缩略图。QtConcurrent 提供了map()函你数来将操作应用到容器中的每一个元素,提供了fitler()函数来选择容器元素,以及指定reduce函数作为选项来组合剩余元素。
一次调用一个耗时运行的操作需要放入另一个线程。在处理过程中,状态信息需要发送会GUI线程。使用QThread,重新实现run函数并根据需要发送信号。使用信号槽的queued连接方式将信号连接到GUI线程的槽函数。
持久运行生存在另一个线程中的对象,根据要求需要执行不同的任务。这意味着工作线程需要双向的通讯。派生一个QObject对象并实现需要的信号和槽,将对象移动到一个运行有事件循环的线程中并通过queued方式连接的信号槽进行通讯。
持久运行生存在另一个线程中的对象,执行诸如轮询端口等重复的任务并与GUI线程通讯。同上,但是在工作线程中使用一个定时器来轮询。尽管如此,处理轮询的最好的解决方案是彻底避免它。有时QSocketNotifer是一个替代。

6 替代线程技术

实际用Qt开发时,也有一些其他技术可以代替多线程实现差不多的效果,

替代技术注解
QEventLoop::processEvents()在一个耗时的计算操作中反复调用QEventLoop::processEvents() 可以防止界面的假死。尽管如此,这个方案可伸缩性并不太好,因为该函数可能会被调用地过于频繁或者不够频繁。
QTimer后台处理操作有时可以方便地使用Timer安排在一个在未来的某一时刻执行的槽中来完成。在没有其他事件需要处理时,时间隔为0的定时器超时事件被相应
QSocketNotifier、QNetworkAccessManager、QIODevice::readyRead()这是一个替代技术,替代有一个或多个线程在慢速网络执行阻塞读的情况。只要响应部分的计算可以快速执行,这种设计比在线程中实现的同步等待更好。与线程相比这种设计更不容易出错且更节能(energy efficient)。在许多情况下也有性能优势。

7 开发中的坑

下面补充了几个在实际开发中遇到的几个坑。

7.1 子线程中操作UI

现象:
在子线程中操作UI直接崩溃。
处理办法:
Qt创建的子线程中是不能对UI对象进行任何操作的,即QWidget及其派生类对象。正确的操作应该是通过信号槽,将一些参数传递给主线程,让主线程去处理。

7.2 信号槽连接方式写错

现象:
连接方式使用了直接连接(DirectConnection),主线程收不到异步线程的消息。
处理办法:
在线程间进行信号槽连接时,使用队列连接方式(QueuedConnection)或者自动连接(AutoConnection)。

7.3 退出界面但没有关闭线程

现象:
退出某个子页面后,过了一会儿弹出上个页面的成功or失败提示。
处理办法:
比如Qthread,可以通过quit()、wait()来关闭线程,有时候需要通过改变线程里面循环的判断条件来使之结束。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt Creator 多线程读取文件到程序显示 利用QT Creator多任务读取一个文档到程序里 为了防止直接读取文件里的内容太大而发生卡顿,于是多线程读取将更高效的解决这个问题。 效果图如下: 其中pro文件无需改动,默认就好,头文件h里面的内容为 #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MyObj; class MyObj : public QObject { Q_OBJECT public: MyObj(); //新的线程 signals: void toLine(QString line); private slots: void doWork(); }; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void appendText(QString); //定义一个槽 private: Ui::MainWindow *ui; QThread *t; MyObj *obj; }; #endif // MAINWINDOW_H 而MAIN主文件的内容为了防止中文乱码做了如下修改: #include "mainwindow.h" #include #include int main(int argc, char *argv[]) { QApplication a(argc, argv); //设置中文字体 防止乱码 a.setFont(QFont("Microsoft Yahei", 9)); //设置中文编码 #if (QT_VERSION <= QT_VERSION_CHECK(5,0,0)) #if _MSC_VER QTextCodec *codec = QTextCodec::codecForName("GBK"); #else QTextCodec *codec = QTextCodec::codecForName("UTF-8"); #endif QTextCodec::setCodecForLocale(codec); QTextCodec::setCodecForCStrings(codec); QTextCodec::setCodecForTr(codec); #else QTextCodec *codec = QTextCodec::codecForName("UTF-8"); QTextCodec::setCodecForLocale(codec); #endif MainWindow w; w.show(); return a.exec(); } 接下来重点来了,源文件CPP里为 #include "mainwindow.h" #include "ui_mainwindow.h" #include #include #include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); t = new QThread(); //QThread obj = new MyObj(); obj->moveToThread(t); qDebug()<<"main thread:"<<QThread::currentThread(); connect(t,SIGNAL(started()), obj, SLOT(doWork())); connect(obj,SIGNAL

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值