【QT Creator学习记录】QTConcurrent,QFuture,QFutureWatcher示例代码

目录

一、QTConcurrent是用于做什么的?

二、单线程示例Single Threaded 

三、多线程示例Multi-Threaded

1. 新建线程——run

2. 获取返回值——QFuture

3. 信号槽使用——QFutureWatcher

4. 调用成员函数——run

一、QTConcurrent是用于做什么的?

QT官方文档[5.12.0]

  • The QtConcurrent namespace provides high-level APIs that make it possible to write multi-threaded programs without using low-level threading primitives such as mutexes, read-write locks, wait conditions, or semaphores. Programs written with QtConcurrent automatically adjust the number of threads used according to the number of processor cores available. This means that applications written today will continue to scale when deployed on multi-core systems in the future.
  • QtConcurrent includes functional programming style APIs for parallel list processing, including a MapReduce and FilterReduce implementation for shared-memory (non-distributed) systems, and classes for managing asynchronous computations in GUI applications:

QtConcurrent(并发)是一个命名空间,提供一些关于多线程的高级API。

简单来说就是搞多线程用的,与低级的那些方法相比更为方便。

二、单线程示例Single Threaded 

写个方法,用来数数,1秒数一个

#include "mainwindow.h"
#include 
#include 
#include 

using namespace Qt;

void getCount();

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //MainWindow w;
    //w.show();

    qDebug()<<"start"<

 输出结果:

/* start 0 1 2 3 4 end */ 

从start到end中间是要等待的,也就是说getcount()方法没结束时,是无法运行后面代码的。

三、多线程示例Multi-Threaded

1. 新建线程——run

使用concurrent需要在pro项目文件中加入代码行,并添加头文件。

  • pro项目文件添加模块QT += concurrent (直接文件开头新增一行写入)
  • 添加头文件#include

代码示例

#include "mainwindow.h"

#include 
#include 
#include 
#include 

using namespace Qt;

void getCount();

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //MainWindow w;
    //w.show();

    qDebug()<<"start"<

运行结果:

/* start  end 0 1 2 3 4  */ 

因为getCount()丢给了新线程,原线程可以跳过等待直接运行后面的语句。

  • Q: 这有什么用处?

    A:例如当我们写好一个软件,用户操作界面时,一些耗时的操作我们不能让用户一直等着,允许在耗时的代码运行时,用户去做些其他事情。就像我们用电脑卸载大型游戏时,如果卸载的过程中啥都做不了,必须等这个过程结束,就很离谱。
  • 可以同时做多件也就是我们说的异步编程:异步是指一个任务的执行不需要等待前一个任务的完成。在异步编程中,程序不会一直等待一个操作的完成,而是可以继续执行其他任务,等待操作完成后再返回结果。异步编程可以提高程序的响应速度避免程序一直阻塞等待任务的完成

2. 获取返回值——QFuture

QT官方文档[6.5.2]

  • QFuture allows threads to be synchronized against one or more results which will be ready at a later point in time. The result can be of any type that has default, copy and possibly move constructors. If a result is not available at the time of calling the result(), resultAt(), results() and takeResult() functions, QFuture will wait until the result becomes available. You can use the isResultReadyAt() function to determine if a result is ready or not. For QFuture objects that report more than one result, the resultCount() function returns the number of continuous results. This means that it is always safe to iterate through the results from 0 to resultCount(). takeResult() invalidates a future, and any subsequent attempt to access result or results from the future leads to undefined behavior. isValid() tells you if results can be accessed.
  • QFuture provides a Java-style iterator (QFutureIterator) and an STL-style iterator (QFuture::const_iterator). Using these iterators is another way to access results in the future.
  • If the result of one asynchronous computation needs to be passed to another, QFuture provides a convenient way of chaining multiple sequential computations using then(). onCanceled() can be used for adding a handler to be called if the QFuture is canceled. Additionally, onFailed() can be used to handle any failures that occurred in the chain. Note that QFuture relies on exceptions for the error handling. If using exceptions is not an option, you can still indicate the error state of QFuture, by making the error type part of the QFuture type. For example, you can use std::variant, std::any or similar for keeping the result or failure or make your custom type.
  • Q:刚刚的getcount函数没有返回值,如果有返回值且我们需要用到这个值,该怎么办?
    A :用QFuture来获取。
     
  • 首先将方法getCount改为需要一个int参数返回一个int参数的方法。
int getCount(int n);
  • 传递参数直接在QtConcurrent::run方法中加入参数值。(可以不止一个,按顺序传)
//这里我们传参数4
QFuture f = QtConcurrent::run( getCount,4);

代码示例 

#include "mainwindow.h"

#include 
#include 
#include 
#include 


using namespace Qt;

int getCount(int n);

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //__FUNCTION__ 得到当前运行的函数名称 
    //QThread::currentThread()当前线程标识
    qDebug() << __FUNCTION__   << QThread::currentThread();

    QFuture f = QtConcurrent::run( getCount,4);

     // 会阻塞线程
    qDebug()<< "f.result: "<

运行结果: 

/*

qMain    QThread(0x25f41186dd0)

getCount   QThreadPoolThread(0x25f42b0a430, name = "Thread (pooled)")

0 1 2 3

f.result: 4

f.resultCount: 1

end

*/

  • 由于要等待新线程的返回值,所以会造成线程阻塞,直到被丢尽新线程中的getCount方法运行结束后,主线程才能进一步运行下去。
  • 我们可以看到QFuture可以用来存储 QtConcurrent::run方法的相关结果,例如,return的值可以用result方法获取,resultCount方法可以得知一共有多少有效的返回值。
  • 主线程qmain的线程标识为QThread(0x25f41186dd0)
    新线程在线程池中,线程标识为QThreadPoolThread(0x25f42b0a430, name = "Thread (pooled)")
  •  Q: 线程池是什么?

若需要频繁的创建线程,建议使用线程池。线程池可以维护一定数量的线程,只需要向线程池中丢任务就行,线程池会根据自己可使用线程数进行任务调度。

可以理解为多个窗口服务,只排一个队列,当有窗口空闲时,队列第一个人过去办理事务;线程池就是这个办事单位,它维护的线程数量就是窗口数量,任务就是排队的人。

来源:Qt线程池_贝勒里恩的博客-CSDN博客

3. 信号槽使用——QFutureWatcher

  • 由于QFuture 类本身未继承QObject,所以无法直接使用信号槽,所以需要另外一个监视类 QFutureWatcher。
  • QFutureWatcher 提供了有关 QFuture 的信息和通知,使用 setFuture() 函数开始监视一个特定的 QFuture,函数 future() 则返回由 setFuture() 设置的 future。

代码示例

#include 
#include 
#include 
#include 
#include 

int getCount(int n){			// 有参有返回
    qDebug() << "进程池进程开始,当前线程标识:" << QThread::currentThread ();
    int i = 0;
    while (i < n) {
        qDebug() << i;
        Sleep(1000);
        ++i;
    }
    qDebug() << "进程池进程开始,当前线程标识:" << QThread::currentThread ();
    return n;
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    qDebug() << "主进程前,当前线程标识:" << QThread::currentThread ();

    QFuture f = QtConcurrent::run( getCount, 4 );

    //创建监视器w
    QFutureWatcher w;

    //将w设置监视线程结果f
    w.setFuture (f);

    QObject::connect (&w,&QFutureWatcher::finished,[&]{
       qDebug()<< "[进程监视] 当前进程:"<< QThread::currentThread ();
       qDebug()<< "[进程监视] 当前进程输出:" <

运行结果:

/*
主进程前,当前线程标识: QThread(
0x1dcfc5e6dd0)
主进程后,当前线程标识: QThread(
0x1dcfc5e6dd0)
进程池进程开始,当前线程标识: QThreadPoolThread(
0x1dcfddca040, name = "Thread (pooled)")
0
1
2
3
进程池进程开始,当前线程标识: QThreadPoolThread(
0x1dcfddca040, name = "Thread (pooled)")
[进程监视] 当前进程: QThread(0x1dcfc5e6dd0)
[进程监视] 当前进程输出: 4 1
*/

  • 我们可以看到代码执行的顺序,主程序的代码全部执行完毕后,新进程才开始工作,是因为我们这次将f.result ()  f.resultCount() 都设置在等线程运行完毕后执行,这样就不会导致线程阻塞。 
  • 运行顺序:主线程 → 子线程主线程(由于子线程完成发送finished信号→触发槽)
  • QObject::connect (监视器名称,信号,lambda表达式),含义为连接信号槽,当监视器w接收到信号finished,执行表达式中的代码。这里的表达式相当于匿名槽函数。自己定义一个槽函数然后触发也是一样的。
  • 所以QFuture的功能就显而易见,它是用于服务QFuture的,是为了弥补QFuture因没有继承QObject而导致无法使用信号槽而生的。
  • 关于使用信号槽的意义,可以防止不必要的线程阻塞,我想得到函数给我的返回值,但是该返回值并不影响接下来我程序的其他操作,那么我就可以丢给信号槽来完成,当qt检测到QFuture取得结果后,再将返回值给到我。

4. 调用成员函数——run

代码示例

#include 
#include 
#include 
#include 
#include 

class Test{		// 声明一个类
public:
    int getCount(int n){
        qDebug() << "进程池进程开始,当前线程标识:" << QThread::currentThread ();
            int i = 0;
        while (i < n) {
            qDebug() << i;
            Sleep(1000);
            ++i;
        }
        qDebug() << "进程池进程开始,当前线程标识:" << QThread::currentThread ();
            return n;
    }
};



int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    qDebug() << "主进程前,当前线程标识:" << QThread::currentThread ();
    Test test;
    //QT5写法:QFuture f = QtConcurrent::run( &test, &Test::getCount, 4 );
    //QT6
    QFuture f = QtConcurrent::run( &Test::getCount, &test, 4 );

    QFutureWatcher w;
    w.setFuture (f);
    QObject::connect (&w,&QFutureWatcher::finished,[&]{
        qDebug()<< "[进程监视] 当前进程:"<< QThread::currentThread ();
        qDebug()<< "[进程监视] 当前进程输出:" <

运行结果同上。

  • 需要注意的就是QT5与QT6调用成员方法并不同,调换下前两个参数位置即可。 

参考文章:

 Qt 之 Concurrent 4、【例】并发函数Run用法示例_qtconcurrent::run_hitzsf的博客-CSDN博客

 Qt QtConcurrent之 Run 函数用法_qtconcurrent::run_不脱发的码农~~~~的博客-CSDN博客

 QtConcurrent多线程 - run()与QFuture_qtconcurrent::run-CSDN博客

感谢前辈们提供的经验( •̀ ω •́ ) ✧让我一个小菜鸡也能慢慢朝前爬

你可能感兴趣的:(QT,Creator,qt,学习,开发语言)