Qt6教程之三(9) 多线程、线程间通讯、线程调度

在程序开发中,当遇到一些耗时任务时,我们希望操作界面能流畅操作而耗时任务也能继续进行,那么此时多线程就派上用场了。

所谓多线程,就是在主线程的基础上,再次新增多个线程,用于执行耗时任务,待任务执行完毕后,把结果告知主线程更新界面即可!

在Qt框架中,实现多线程的方式大概有三种,分别是:

1、继承QThread类并重写其run方法,在run方法里面执行耗时任务;

2、使用可重用线程池类QThreadPool,需要重新实现QRunnable::run()并实例化子类化的QRunnable,然后在run方法里面写执行逻辑;

3、使用Qt并发类Concurrent ,这个类功能非常强大,会自动根据电脑核心数量来执行任务。

多线程

下面将对上述三种线程进行一一演示。

1、继承QThread类并重写其run方法,在run方法里面执行耗时任务; 下面将使用多线程来实现更新进度条的实例进行演示:

代码(为了方便,直接把两个类的代码集中放置,各位读代码时注意区分):

线程类:

MyThread.h




#ifndef MYTHREAD_H
#define MYTHREAD_H

#include 

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


protected:
    virtual void run() Q_DECL_OVERRIDE ;


 signals:
    void ready(int value);

};

#endif // MYTHREAD_H





MyThread.cpp



#include "mythread.h"

MyThread::MyThread(QObject *parent) : QThread{parent}
{

}

void MyThread::run()
{
    static int value=0;
    while (value<100) {
        msleep(100);
        ++value;

        if(value==100) value=0;

        emit ready(value);
    }
}

界面绘制显示类(主线程类):

main.cpp

#include "mainwindow.h"

#include 

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

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include "mythread.h"

#include 

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    MyThread *thread;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->progressBar->setRange(0,100);
    ui->progressBar->setValue(0);

    thread =new MyThread(this);
    thread->start();

    connect(thread,SIGNAL(ready(int)),ui->progressBar,SLOT(setValue(int)));
    connect(thread,SIGNAL(finished()),thread,SLOT(deleteLater()));
}

MainWindow::~MainWindow()
{
    delete ui;

}

运行效果:线程会一直更新GUI界面,当到达100时会返回0继续执行,永不停止!

Qt6教程之三(9) 多线程、线程间通讯、线程调度_第1张图片

其实还有另外一种变种写法,官方推荐这种写法,他就是继承QObject,然后使用move方法把耗时任务移动至线程中执行。

继承OBject类代码:

MyObjectThread.h

#ifndef MYOBJECTTHREAD_H
#define MYOBJECTTHREAD_H

#include 



class MyObjectThread : public QObject
{
    Q_OBJECT

public:
    explicit MyObjectThread(QObject *parent = nullptr);

signals:
    void workFinish(int value);

public slots:
    void doWork();

};

#endif // MYOBJECTTHREAD_H

MyObjectThread.cpp

#include "myobjectthread.h"
#include

MyObjectThread::MyObjectThread(QObject *parent)
    : QObject{parent}
{

}

void MyObjectThread::doWork()
{
    static int value=0;
    while (value<100) {
        QThread::msleep(100);
        ++value;

        if(value==100) value=0;

        emit workFinish(value);
    }
}

main.cpp

#include "mainwindow.h"

#include 

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

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include "myobjectthread.h"


#include 

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QThread *thread_move;
    MyObjectThread *othred;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    

    ui->progressBar_2->setRange(0,100);
    ui->progressBar_2->setValue(0);

    thread_move=new QThread();
    othred=new MyObjectThread();
    othred->moveToThread(thread_move);
    connect(thread_move,SIGNAL(started()),othred,SLOT(doWork()));

    thread_move->start();

    connect(othred,SIGNAL(workFinish(int)),ui->progressBar_2,SLOT(setValue(int)));
connect(thread_move,SIGNAL(finished()),thread_move,SLOT(deleteLater()));

}

MainWindow::~MainWindow()
{
    delete ui;

}

运行效果(两种线程书写方式分别操作两个进度条):

Qt6教程之三(9) 多线程、线程间通讯、线程调度_第2张图片

2、使用可重用线程池类QThreadPool,需要重新实现QRunnable::run()并实例化子类化的QRunnable,然后在run方法里面写执行逻辑;

此示例创建了三个类,均继承QRunnable和QObject,然后再由QThreadPool统一管理,三个类分别实现三个控件的控制,代码如下:

task_1类

#ifndef TASK_1_H
#define TASK_1_H

#include 
#include 

class task_1 : public QObject ,public QRunnable
{
    Q_OBJECT
public:
    task_1();
    ~task_1();


signals:
    void print(QString str);


public:
    void run();
};

#endif // TASK_1_H
#include "task_1.h"
#include

task_1::task_1()
{

}

task_1::~task_1()
{

}

void task_1::run()
{

    while (true) {
        QThread::msleep(100);


        emit print("hello world task_1 ! \n");
    }
}

task_2类

#ifndef TASK_2_H
#define TASK_2_H

#include 
#include 
#include 


class task_2 : public QObject,public QRunnable
{
    Q_OBJECT
public:
    task_2();

    // QRunnable interface
public:
    void run();

signals:
    void dialValue(int value);
};

#endif // TASK_2_H
#include "task_2.h"

#include 

task_2::task_2()
{

}

void task_2::run()
{
    static int count=0;

    while (true) {
        QThread::msleep(100);
        count++;

        if(count==1000) break;

        emit dialValue(count);
    }
}

task_3类

#ifndef TASK_3_H
#define TASK_3_H

#include 
#include 

class task_3 : public QObject,public QRunnable
{
    Q_OBJECT
public:
    explicit task_3(QObject *parent = nullptr);

signals:
    void valueChange(int value);



public:
    virtual void run() override;
};

#endif // TASK_3_H
#include "task_3.h"

#include 

task_3::task_3(QObject *parent)
    : QObject{parent}
{

}

void task_3::run()
{
    static int value=0;

    while (true) {
        QThread::msleep(100);
        value++;

        if(value==100)  break;

        emit valueChange(value);
    }
}

主控制类mainwindow:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include "task_1.h"
#include "task_2.h"
#include "task_3.h"

#include 
#include 
#include 

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QThreadPool *ThreadPool;
    task_1 *task1;
    task_2 *task2;
    task_3 *task3;
    QTimer *timer;

private slots:
    void threadCount();
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "./ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    task1=new task_1;
    task2=new task_2;
    task3=new task_3;

    timer=new QTimer(this);
    timer->start(500);

    ThreadPool = QThreadPool::globalInstance();
    ThreadPool->setMaxThreadCount(10);
    ThreadPool->start(task1);
    ThreadPool->start(task2);
    ThreadPool->start(task3);

    connect(task1,SIGNAL(print(QString)),ui->textEdit,SLOT(append(QString)));
    connect(task2,SIGNAL(dialValue(int)),ui->lcdNumber,SLOT(display(int)));
    connect(task3,SIGNAL(valueChange(int)),ui->progressBar,SLOT(setValue(int)));

    connect(timer,SIGNAL(timeout()),this,SLOT(threadCount()));

}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::threadCount()
{
     ui->statusbar->showMessage("current activity thread count is:" +QString::number(ThreadPool->activeThreadCount()));
}

#include "mainwindow.h"

#include 

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

    //close window after delete windows point
    w.setAttribute(Qt::WA_DeleteOnClose, true);
    return a.exec();
}

运行效果:

Qt6教程之三(9) 多线程、线程间通讯、线程调度_第3张图片

3、使用Qt并发类QtConcurrent 

QtConcurrent类是一个并发类,是一个高级API,对基础的QThread进行了更高层级的封装。

QtConcurrent命名空间提供了高级API,可以在不使用低级线程原语的情况下编写多线程程序。

关键示例代码:

#include "mainwindow.h"
#include "./ui_mainwindow.h"

#include 
#include 
#include


extern void test(){
    while (true) {
        QThread::msleep(1000);
        qDebug()<<"hello world!";
    }

}

extern int sum(int a,int b){
    b=a*a+b;
    a=b*a+a;

    return a+b;
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->progressBar->setRange(0,100);

   auto localRun = QtConcurrent::run(test);
   QFuture result= QtConcurrent::run(sum,int(100),int(99));
   int re=result.result();
   qDebug()<

线程间通信

所谓线程间通信就是不同线程之间的交流与回馈,通常有以下四种方式:
1、使用共享内存:向操作系统申请一块公共内存空间,然后两个线程根据约定放置指定内容到该内存中,二者轮流访问或修改共享内存区数据来达到数据共享及相互通信的目地。

2、使用singal/slot机制,把数据从一个线程传递到另外一个线程。这是Qt框架特有的机制,使用起来非常方便、快捷,在不追求极致效率的前提下,推荐使用这种方式。

3、使用网络编程,通过网卡发送消息来打到通信的目地。

4、系统信号量,这种方式比较繁琐,但是非常高效和安全。
 

线程调度

线程调度是指对线程的管理,以达到合理利用资源的目的。但是通常实际开发中,我们一般均使用Qt或c++提供的线程管理类来进行管理(调度),这种方式快捷、安全、高效,不会因调度问题引发其他异常,毕竟是官方提供的,其可信度还是很高的。

当然,若大家有兴趣,也可以自己实现线程调度的代码编写,下面是几种常见的线程调度算法介绍:

1. 先来先服务
        在所有调度算法中,最简单的是非抢占式的先先来先服务(Firis-Come Fist-Severed,FCFS)算法。使用该算法,进程按照它们请求CPU的顺序使用CPU。基本上,有一个就绪进程的单一队列。早上,当第一个作业从外部进人系统,就立即开始并允许运行它所期望的时间。不会中断该作业,因为它需要很长的时间运行。当其他作业进人时,它们就被安排到队列的尾部。当正在运行的进程被阻塞时,队列中的第一个进 程就接着运行。当被阻塞的进程变为就绪时,就像一个新来到的作业样,排到队列的末尾。

      这个算法的主要优点是易于理解并且便于在程序中运用。在这个算法中,一个单链表记录了所有就绪进程。要选取一个进程运行,只要从该队列的头部移走一个进程即可;要添加一个新的作业或阻塞一个进程,只要把该作业或进程附加在相应队列的末尾即可。  

2. 最短作业优先
         现在来看一种适用于运行时可以预知的另一个非抢占式的批处理调度算法。当输人队列中有若干个同等重要的作业被启动时,调度程序应使用最短作业优先(Shortest Job First,SJF)算法。

3. 最短剩余时间优先
           最短作业优先的抢占式版本是最短剩余时间优先( Shortest Remaining Time Next,SRTN)算法。使用这个算法,调度程序总是选择其剩余运行时间最短的那个进程运行。有关的运行时间必须提前掌握。当一个新的作业到达时,其整个时间同当前进程的剩余时间做比较。如果新的进程比当前运行进程需要更少的时间,当前进程就被挂起,而运行新的进程。这种方式可以使新的短作业获得良好的服务。

4. 最高响应比优先算法
        在批处理系统中,最高响应比优先算法(Highest Response Rate First,HRRF)的性能是介于先来先服务和最短作业优先算法之间的折中算法。先来先服务算法在调度中最为公平,但是一且出现计算密集型的长作业则会对其他进程造成较长时间的等待;最短作业优先算法又偏好短作业,当短作业源源不断进人后备池时,长作业将会长时间滞留在后备池中,其运行将得不到保证,出现这种现象我们称为长作业处于“饥饿( starvation)”状态。

        如果能为每个作业引人响应比,情况就会有所改善。响应比的计算式为:

        响应比 Rp= (等待时间+预计运行时间)/预计运行时间=周转时间/预计运行时间

        每个作业随着在后备池等待时间的增长其响应比也不断增长,而且,预计运行时间越短的作业响应比增长越快。最高响应比优先算法在每次调度时选择响应比最高的作业投人运行,这种算法较好地适应了长短作业混合的系统,使得调度的性能指标趋于合理。

        最高响应比优先算法在一定程度上改善了调度的公平性和调度的效率,响应比在每次调度前进行计算,作业运行期间不计算。计算需要消耗系统的资源,存在一定的系统开销。  

5. 轮转法  
        轮转(Round-Robin,RR)算法最早来自分时系统。轮转法的基本思想是,将CPU的处理时间划分成一个个时间片,就绪队列中的诸进程轮流运行一个时间片。当时间片结束时,就强迫运行进程让出CPU,该进程进人就绪队列,等待下一-次调度。同时,进程调度又去选择就绪队列中的一个进程,分配给它一一个时间片,以投人运行。如此轮流调度,使得就绪队列中的所有进程在一个有限的时间T内都可以依次轮流获得一个时间片的处理机时间,从而满足了系统对用户分时响应的要求。

6. 最高优先级算法
        最高优先级(Highest Priority First,HPF)进程(线程)调度每次将处理机分配给具有最高优先级的就绪进程(线程)。进程(线程)的优先级由进程(线程)优先数决定。

7. 多级反馈队列算法
在实际的计算机系统中,进程(线程)的调度模式往往是几种调度算法的结合。例如,可以以最高优先级算法作为主要的调度模式,但对于具有相同优先数的进程((线程)则按先进先出算法法处理。又如,可以将时间片轮转算法和最高优先级算法结合,对于具有相同优先数的进程(线程)按时间片轮转调度算法处理。多级队列反馈法就是综合了先进先出调度算法、时间片轮转算法和可抢占式最高优先级算法的一种进程(线程 )调度算法。

8. 最短进程优先
        对于批处理系统而言,由于最短作业优先常常伴随着最短响应时间,所以如果能够把它用于交互进程,那将是非常好的。交互进程通常遵循下列模式:等待命令,执行命令,等待命令,执行命令,如此反复。如果将每一条命令的执行看作一个独立的“作业”,则可以通过首先运行最短的作业来使用响应时间最短。这里唯一的问题是如何从当前可运行进程中找出最短的那一个进程。

9. 实时系统中的调度算法
        实时系统是一种时间起着主导作用的系统,即系统的正确性不仅取决于计算的逻辑结果,   而且还依赖于产生结果的时间。典型的,外部物理设备给计算机发送了一个信号,则计算机必须在一个确定的时间范围内恰当地做出反应。
 

下一篇博客

Qt6教程之三(10) 异步与并发编程_折腾猿王申兵的博客-CSDN博客本篇博客主要介绍并发与异步编程,https://blog.csdn.net/XiaoWang_csdn/article/details/129718323?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22129718323%22%2C%22source%22%3A%22XiaoWang_csdn%22%7D

上一篇博客

Qt6教程之三(8 )多进程、进程间通讯和调度_折腾猿王申兵的博客-CSDN博客本篇博客主要介绍在Qt中的多进程、进程间通讯及进程间调度的基本使用!https://blog.csdn.net/XiaoWang_csdn/article/details/129369363

你可能感兴趣的:(Qt学习,程序开发,c++,qt,ui)