• QT 中多线程实现方法总结


    第一: 用QtConcurrentRun类,适合在另一个线程中运行一个函数。不用继承类,很方便

    第二:用QRunnable和QThreadPool结合。继承QRunnable,重写run函数,然后用QThreadPool运行这个线程。缺点是不能使用信号和槽

    第三:继承QObject 使用moveToThread方法

    第四:继承QThread,重写run函数。

    /*****************************************************

    1.多线程的理解

    在操作系统中线程和进程划分。

    操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。

    线程之间相互独立,抢占式执行。对于单核CPU来说同一时刻只能有一个线程执行,一个进程执行。

    但是由于CPU不断在这些进程间轮换执行,速度相对人的反应很快,不容易察觉。

    既然这样,为什么要使用多线程呢?

    a.对于多核cpu,多线程程序充分利用硬件优势

    b.对于单核cpu,由于线程上下文的切换会降低整体运行效率。但是为了防止执行耗时操作时界面假死,我们必须把耗时操作单独放在线程中后台执行,防止阻塞主线程无法刷新窗口。

    我们来看一下程序的阻塞和非阻塞

    这里主要说对于线程之间的公共资源,同时只能由一个线程操作,在此期间其他线程访问将会被挂起直到上一次访问结束,同样客户端执行界面刷新的主线程也会挂起。

    非阻塞指的是,一个线程的操作不会阻塞其他线程对事件的接受和处理。

    同步和异步

    这里说的执行一个操作必须等待执行完成,下面的逻辑才会继续执行,是为同步执行代码

    对函数调用后,不会等待执行结果,继续执行下面的代码,是为异步执行。

    2.线程使用

    QT中多线程得两种处理方法

    使用线程在程序执行长时间操作得时候弹出进度条

    使用线程可以把占据时间长得程序中任务放到后台去处理

    其中一种是继承QThread得run函数,另外一种是把一个继承于QObject得类转移到一个Thread里。

    1.继承QThread

    QThread继承类只有run函数是在新线程里跑的,其他函数在创建QThread的线程中运行

     新建一个线程类ExportThread:QThread ,把耗时操作放在其中run函数中

    2.把一个继承于QObject的类转移到一个Thread里

    创建一个继承自QObject类得类对象object,使用object.moveToThread(QThread *);

    3.线程类中得槽函数在哪个线程中执行得问题

    对于方法1中,槽函数在创建线程类对象得线程(一般是主线程)中执行

    对于方法2中,槽函数在次线程中执行,通过信号槽调用,直接调用则都在调用线程中执行,

    所以要把耗时操作放在槽函数中,外面信号触发,

    具体需要参考,connect函数中表示连接方式得参数

    如下:

    同步调用:信号发出后,当前线程等待槽函数执行完毕才能执行剩余代码。

    异步调用:信号发出后,立即执行剩余逻辑,不关心槽函数什么时候执行。

    AutoConnection   信号和槽同一线程时,直接联,不同线程时,队列联

    DirectConnection  直接联,在主线程中执行,同步调用,不依赖QT事件循环

    QueueConnection 队列,次线程中执行,异步调用,槽函数所在对象得线程必须启用QT事件循环

    BlockingQueuedConnection 阻塞联,同步调用, 槽函数在次线程中执行,用信号量实现阻塞,     

    槽函数所在对象得线程必须启用QT事件循环,此连接只能用于发

     出信号得线程和槽函数执行线程不同得情况。

    要么在发射信号得线程中执行

    要么在接受者依附得线程中执行

    线程安全

    /*********************************************************************

    一、QThread类的run
    一、实现方法:

    新建一个集成QThread的类,重写虚函数run,通过run启动线程

    二、示例:

    class WorkerThread : public QThread
    {
    Q_OBJECT
    void run() override {
    QString result;
    /* ... here is the expensive or blocking operation ... */
    emit resultReady(result);
    }
    signals:
    void resultReady(const QString &s);
    };

    void MyObject::startWorkInAThread()
    {
    WorkerThread *workerThread = new WorkerThread(this);
    connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
    connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
    workerThread->start();
    }
    三、特点:

    1、优点:可以通过信号槽与外界进行通信。
    2、缺点:1每次新建一个线程都需要继承QThread,实现一个新类,使用不太方便。
    要自己进行资源管理,线程释放和删除。并且频繁的创建和释放会带来比较大的内存开销。
    3、适用场景:QThread适用于那些常驻内存的任务。

    二、QThread类的moveToThread
    一、实现方法:

    创建一个继承QObject的类(myobject),然后new一个Qthread,并把创建的myobject类movetothread到创建好的子线程中,然后start子线程,这样就实现了一个子线程。主线程通过发送信号,调用myobject中的方法,从而实现在子线程中的计算。

    二、示例:


    class Worker : public QObject
    {
    Q_OBJECT

    public slots:
    void doWork(const QString ¶meter) {
    QString result;
    /* ... here is the expensive or blocking operation ... */
    emit resultReady(result);
    }

    signals:
    void resultReady(const QString &result);
    };

    class Controller : public QObject
    {
    Q_OBJECT
    QThread workerThread;
    public:
    Controller() {
    Worker *worker = new Worker;
    worker->moveToThread(&workerThread);
    connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
    connect(this, &Controller::operate, worker, &Worker::doWork);
    connect(worker, &Worker::resultReady, this, &Controller::handleResults);
    workerThread.start();
    }
    ~Controller() {
    workerThread.quit();
    workerThread.wait();
    }
    public slots:
    void handleResults(const QString &);
    signals:
    void operate(const QString &);
    };
    三、特点:

    MovetoThreadTest.cpp 中用到了窗体中的几个按钮,在如用代码是首相创建一个窗体,在窗体添加按钮,然后跟据按钮的名字进行连接即可;

    主线程如果要在子线程中运行计算必须通过发信号的方式调用,或者通过控件的信号;需要说明在创建连接时如果第五的参数为DirectConnection时,调用的槽函数是在主线程中运行;如果想通过子线程向主线程调用方法,也必须通过发信号的方式触发主线程的函数。

    Qt::AutoConnection,t::DirectConnection,t::QueuedConnection,t::BlockingQueuedConnection,t::UniqueConnection

    Qt::AutoCompatConnection这里面一共有六种方式。

    前两种比较相似,都是同一线程之间连接的方式,不同的是Qt::AutoConnection是系统默认的连接方式。这种方式连接的时候,槽不是马上被执行的,而是进入一个消息队列,待到何时执行就不是我们可以知道的了,当信号和槽不是同个线程,会使用第三种QT::QueueConnection的链接方式。如果信号和槽是同个线程,调用第二种Qt::DirectConnection链接方式。
    第二种Qt::DirectConnection是直接连接,也就是只要信号发出直接就到槽去执行,无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行,一旦使用这种连接,槽将会不在线程执行!。
    第三种Qt::QueuedConnection和第四种Qt::BlockingQueuedConnection是相似的,都是可以在不同进程之间进行连接的,不同的是,这里第三种是在对象的当前线程中执行,并且是按照队列顺序执行。当当前线程停止,就会等待下一次启动线程时再按队列顺序执行 ,等待QApplication::exec()或者线程的QThread::exec()才执行相应的槽,就是说:当控制权回到接受者所依附线程的事件循环时,槽函数被调用,而且槽函数在接收者所依附线程执行,使用这种连接,槽会在线程执行。
    第四种Qt::BlockingQueuedConnection是(必须信号和曹在不同线程中,否则直接产生死锁)这个是完全同步队列只有槽线程执行完才会返回,否则发送线程也会等待,相当于是不同的线程可以同步起来执行。
    第五种Qt::UniqueConnection跟默认工作方式相同,只是不能重复连接相同的信号和槽;因为如果重复链接就会导致一个信号发出,对应槽函数就会执行多次。
    第六种Qt::AutoCompatConnection是为了连接QT4 到QT3的信号槽机制兼容方式,工作方式跟Qt::AutoConnection一样。显然这里我们应该选择第三种方式,我们不希望子线程没结束主线程还要等,我们只是希望利用这个空闲时间去干别的事情,当子线程执行完了,只要发消息给主线程就行了,到时候主线程会去响应。

    moveToThread对比传统子类化Qthread更灵活,仅需要把你想要执行的代码放到槽,movetothread这个object到线程,然后拿一个信号连接到这个槽就可以让这个槽函数在线程里执行。可以说,movetothread给我们编写代码提供了新的思路,当然不是说子类化qthread不好,只是你应该知道还有这种方式去调用线程。

    轻量级的函数可以用movethread,多个短小精悍能返回快速的线程函数适用 ,无需创建独立线程类,例如你有20个小函数要在线程内做, 全部扔给一个QThread。而我觉得movetothread和子类化QThread的区别不大,更可能是使用习惯引导。又或者你一开始没使用线程,但是后边发觉这些代码还是放线程比较好,如果用子类化QThread的方法重新设计代码,将会有可能让你把这一段推到重来,这个时候,moveThread的好处就来了,你可以把这段代码的从属着movetothread,把代码移到槽函数,用信号触发它就行了。其它的话movetothread它的效果和子类化QThread的效果是一样的,槽就相当于你的run()函数,你往run()里塞什么代码,就可以往槽里塞什么代码,子类化QThread的线程只可以有一个入口就是run(),而movetothread就有很多触发的入口。

    三、QRunnalble的run
    Qrunnable是所有可执行对象的基类。我们可以继承Qrunnable,并重写虚函数void QRunnable::run () 。我们可以用QThreadPool让我们的一个QRunnable对象在另外的线程中运行,如果autoDelete()返回true(默认),那么QThreadPool将会在run()运行结束后自动删除Qrunnable对象。可以调用void QRunnable::setAutoDelete ( bool autoDelete )更改auto-deletion标记。需要注意的是,必须在调用QThreadPool::start()之前设置,在调用QThreadPool::start()之后设置的结果是未定义的。

    一、实现方法:

    1、继承QRunnable。和QThread使用一样, 首先需要将你的线程类继承于QRunnable。

    2、重写run函数。还是和QThread一样,需要重写run函数,run是一个纯虚函数,必须重写。

    3、使用QThreadPool启动线程

    二、示例:

    class Runnable:public QRunnable
    {
    //Q_OBJECT 注意了,Qrunnable不是QObject的子类。
    public:
    Runnable();
    ~Runnable();
    void run();
    };


    Runnable::Runnable():QRunnable()
    {

    }

    Runnable::~Runnable()
    {
    cout<<"~Runnable()"< }

    void Runnable::run()
    {
    cout<<"Runnable::run()thread :"< cout<<"dosomething ...."< }
    int main(int argc, char *argv[])
    {
    QCoreApplication a(argc, argv);
    cout<<"mainthread :"<

     Runable runObj;
       // QThreadPool::globalInstance()->start(&runObj);
        runObj.setAutoDelete(true);
        QThreadPool *threadPool =QThreadPool::globalInstance();
        threadPool->start(&runObj);
        qDebug() <<"data from GUI thread " << QThread::currentThread();
        threadPool->waitForDone();
     
    


    returna.exec();
    }


    三、特点:

    优点:无需手动释放资源,QThreadPool启动线程执行完成后会自动释放。
    缺点:不能使用信号槽与外界通信。
    适用场景:QRunnable适用于线程任务量比较大,需要频繁创建线程。QRunnable能有效减少内存开销。

    四、QtConcurrent的run
    Concurrent是并发的意思,QtConcurrent是一个命名空间,提供了一些高级的 API,使得在编写多线程的时候,无需使用低级线程原语,如读写锁,等待条件或信号。使用QtConcurrent编写的程序会根据可用的处理器内核数自动调整使用的线程数。这意味着今后编写的应用程序将在未来部署在多核系统上时继续扩展。

    QtConcurrent::run能够方便快捷的将任务丢到子线程中去执行,无需继承任何类,也不需要重写函数,使用非常简单。详见前面的文章介绍,这里不再赘述。

    需要注意的是,由于该线程取自全局线程池QThreadPool,函数不能立马执行,需要等待线程可用时才会运行。

    一、实现方法:

    1  QtConcurrent::run()

      QtConcurrent 是命名空间 (namespace),它提供了高层次的函数接口 (APIs),使所写程序,可根据计算机的 CPU 核数,自动调整运行的线程数目。

      下面是 Qt 例程 runfunction,对应目录为  D:\Qt\Qt5.12.3\Examples\Qt-5.12.3\qtconcurrent\runfucntion

    1.1  .pro 工程文件

      使用 QtConcurrent 模块,需要在 .pro 中添加: QT += concurrent 

    1

    2

    3

    4

    QT += concurrent widgets

    CONFIG += console

    SOURCES += main.cpp   

    1.2  main.cpp

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    #include

    #include

    #include

    #include

    #include "qtconcurrentrun.h"

    using namespace QtConcurrent;

    void func(QString name)

    {

        qDebug() << name << "from" << QThread::currentThread();

    }

    int main(int argc, char **argv)

    {

        QApplication app(argc, argv);

        QFuture<void> fut1 = run(func, QString("Thread 1"));

        QFuture<void> fut2 = run(func, QString("Thread 2"));

        fut1.waitForFinished();

        fut2.waitForFinished();

    }  

      用 QtConcurrent::run() 函数,分别将  func() 运行在两个不同的线程中,输出结果如下: 

    1

    2

    "Thread 1" from QThread(0x1b74fd2ebc0, name = "Thread (pooled)")

    "Thread 2" from QThread(0x1b74fd534e0, name = "Thread (pooled)"

      下面是 QtConcurrent::run() 的详细使用,阅读完 2 和 3,再看上面的 runfunction 例程,就容易理解了。

    2  普通函数

    2.1  将函数运行在某一个线程中 

    1

    2

    extern void func();

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

      如果要为其指定线程池,可以将线程池的指针作为第一个参数传递进去

    1

    2

    3

    extern void func();

    QThreadPool pool;

    QFuture<void> future = QtConcurrent::run(&pool, func);  

    2.2  向该函数传递参数

      需要传递的参数,则跟在函数名之后,依次加入 

    1

    2

    3

    4

    5

    6

    extern void FuncWithArguments(int arg1, const QString &string);

     int integer = ...;

     QString string = ...;

     QFuture<void> future = QtConcurrent::run(FuncWithArguments,integer,string);   

    2.3  获取该函数的计算结果 

    1

    2

    3

    4

    5

    6

    7

    extern QString Func(const QByteArray &input);

    QByteArray byte_array = ...;

    QFuture future = QtConcurrent::run(func, byte_array);

    ...

    QString result = future.result(); 

    3  成员函数

      将类中的成员函数运行在某一个线程中,可将指向该类实例的 引用或指针 作为 QtConcurrent::run 的第一个参数传递进去,

      常量成员函数一般传递 常量引用 (const reference),而非常量成员函数一般传递 指针 (pointer)

    3.1  常量成员函数

       在一个单独的线程中,调用 QByteArray 的常量成员函数 split(),传递给 run() 函数的参数是 bytearray 

    1

    2

    3

    4

    QByteArray bytearray = "hello world";

    QFuture > future = QtConcurrent::run(bytearray, &QByteArray::split, ',');

    ...

    QList result = future.result();   

    3.2  非常量成员函数

       在一个单独的线程中,调用 QImage 的非常量成员函数 invertPixels(),传递给 run() 函数的参数是 &image 

    1

    2

    3

    4

    5

    QImage image = ...;

    QFuture<void> future = QtConcurrent::run(&image, &QImage::invertPixels, QImage::InvertRgba);

    ...

    future.waitForFinished();

    // At this point, the pixels in 'image' have been inverted  

    三、特点:

    //调用外部函数 QFuture f1 =QtConcurrent::run(func,QString("aaa"));

    //调用类成员函数 QFuture f2 =QtConcurrent::run(this,&MainWindow::myFunc,QString("bbb"));

    要为其指定线程池,可以将线程池的指针作为第一个参数传递进去

    向该函数传递参数,需要传递的参数,则跟在函数名之后

    可以用run函数的返回值funIr来控制线程。
    如: funIr.waitForFinished(); 等待线程结束,实现阻塞。
    funIr.isFinished() 判断线程是否结束
    funIr, isRunning() 判断线程是否在运行
    funIr的类型必须和线程函数的返回值类型相同,可以通过
    funIr.result() 取出线程函数的返回值

    缺点,不能直接用信号和槽函数来操作线程函数,eg : 当线程函数结束时,不会触发任何信号。

    /****************************************************************************************

    一:继承Qthread
    方法: 继承自QThread类,重写run函数,通过start启动线程。此实现方法只有run函数内的代码是运行在子线程内。
    例程:
    #ifndef MYTHREAD_H
    #define MYTHREAD_H

    #include

    class MyThread: public QThread
    {
        Q_OBJECT

    public:
        MyThread();
        ~MyThread();

    protected:
        void run(); //虚函数重构

    public:
        void stop();

    private:
        bool m_bFlag;

    };

    #endif // MYTHREAD_H

    #include "mythread.h"
    #include

    MyThread::MyThread()
    {

    }

    MyThread::~MyThread()
    {

    }

    void MyThread::run()
    {
        m_bFlag = true;
        while (m_bFlag) {
            qDebug() << "thread id: "<         QThread::msleep(100);
        }
    }

    void MyThread::stop()
    {
        m_bFlag = false;
    }


    #include "mythread.h"
    #include


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

        MyThread *myThread = new MyThread();

        myThread->start(); //开启线程


        return a.exec();
    }

    特点:
    可以通过信号槽与外界进行通信;
    每次新建一个线程都需要继承QThread类,使用不太方便;
    二:moveToThread
    方法:
    创建一个继承QObject的类(myWork),然后new一个Qthread,并把创建的myWork类movetothread到创建好的子线程中,然后start子线程,这样就实现了一个子线程。主线程通过发送信号,调用myWork中的方法,从而实现在子线程中的计算。
    例程:
    #ifndef MYWORK_H
    #define MYWORK_H

    #include

    class MyWork : public QObject
    {
        Q_OBJECT
    public:
        explicit MyWork(QObject *parent = 0);

    signals:

    public slots:
        void doWork();
    };

    #endif // MYWORK_H

    #include "mywork.h"
    #include
    #include

    MyWork::MyWork(QObject *parent) : QObject(parent)
    {

    }

    void MyWork::doWork()
    {
        for(int i = 0; i < 1000; i++){
            qDebug() << "thread id: "<     }

    }

    #include "mywork.h"
    #include
    #include

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


        MyWork *myWork = new MyWork();
        QThread *myThread = new QThread();

        QObject::connect(myThread,SIGNAL(started()),myWork,SLOT(doWork()));
        QObject::connect(myThread,SIGNAL(finished()),myWork,SLOT(deleteLater()));
        QObject::connect(myThread,SIGNAL(finished()),myThread,SLOT(deleteLater()));

        myWork->moveToThread(myThread);
        myThread->start();

        return a.exec();
    }


    特点:
    moveToThread对比传统子类化Qthread更灵活,仅需要把你想要执行的代码放到槽,movetothread这个object到线程,然后拿一个信号连接到这个槽就可以让这个槽函数在线程里执行。
    轻量级的函数可以用movethread,多个短小精悍能返回快速的线程函数适用 ,无需创建独立线程类,例如你有20个小函数要在线程内做, 全部扔给一个QThread。
    三:QRunnalble的run
    方法:
    继承QRunnable。和QThread使用一样。
    重写run函数。
    使用QThreadPool启动线程
    例程:
    class Runnable:public QRunnable
    {
           //Q_OBJECT   Qrunnable不是QObject的子类。
    public:
           Runnable();
           ~Runnable();
           void run();
    };
     
     
    Runnable::Runnable():QRunnable()
    {
     
    }
     
    Runnable::~Runnable()
    {
           cout<<"~Runnable()"< }
     
    void Runnable::run()
    {
           cout<<"Runnable::run()thread :"< }
    int main(int argc, char *argv[])
    {
           QCoreApplication a(argc, argv);

           Runnable runObj;
           QThreadPool::globalInstance()->start(&runObj);
           returna.exec();
    }

    特点:
    无需手动释放资源,QThreadPool启动线程执行完成后会自动释放。
    不能使用信号槽与外界通信。
    适用场景,需要频繁创建线程。
    四:QtConcurrent::run
    方法:
    Concurrent是并发的意思,QtConcurrent是一个命名空间。使用QtConcurrent编写的程序会根据可用的处理器内核数自动调整使用的线程数。
    QtConcurrent::run能够方便快捷的将任务丢到子线程中去执行,无需继承任何类,也不需要重写函数,使用非常简单。
    需要注意的是,由于该线程取自全局线程池QThreadPool,函数不能立马执行,需要等待线程可用时才会运行。
    编程过程:
    1、首先在.pro文件中加上以下内容:QT += concurrent
    2、包含头文件#include ,然后就可以使用QtConcurrent了
    QtConcurrent::run(func, QString(“Thread 1”)); fut1.waitForFinished();
    例程:
    #include
    #include
    #include

    void myPrint(QString str)
    {
        qDebug()< }

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

        QString str = "123";
        QtConcurrent::run(myPrint,str);

        return a.exec();
    }


    特点:
    简单,快捷
    /*************************************************************

    Qt 多线程使用moveToThread

    Qt有两种多线程的方法,其中一种是继承QThread的run函数,
    另外一种是把一个继承于QObject的类用moveToThread函数转移到一个Thread里。 
    Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。

    具体的使用步骤如下:

    1.从QObject派生一个类,将耗时的工作写在该类的槽函数中。

    2.将派生类对象移动到一个QThread中,该线程需要start。(这一步使用moveToThread)

    3.通过信号连接派生类的槽函数,并通过信号触发槽函数。(槽函数在子线程中执行)

    //tes.h
    #ifndef TES_H
    #define TES_H
    
    #include 
    #include 
    #include 
    #include 
    #include 
    
    class Worker:public QObject
    {
        Q_OBJECT
    public:
        explicit Worker(QObject *parent=0);
        ~Worker();
    
    signals:
        void sig_finish();
    
    public slots:
        void slot_dowork();
    
    };
    
    
    #endif // TES_H

    //tes.cpp
    #include "tes.h"
    
    Worker::Worker(QObject *parent):QObject(parent)
    {
        qDebug()<<"worker()";
    }
    Worker::~Worker()
    {
        qDebug()<<"~worker()";
    }
    void Worker::slot_dowork()
    {
        qDebug()<< "do work,thread id = " << QThread::currentThreadId();
        emit sig_finish();
    }

    //mainwindow.h
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include 
    #include "tes.h"
    
    namespace Ui {
    class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
        void dowork();
    
    private:
        Ui::MainWindow *ui;
        Worker *m_pworker;
        QThread *m_pthread;
    
    signals:
        void sig_dowork();
    
    public slots:
        void slot_finish();
    };
    
    #endif // MAINWINDOW_H

    //mainwindow.cpp
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        qDebug()<<"mainwindow()";
        ui->setupUi(this);
    
        m_pworker = new Worker();
        m_pthread = new QThread();
        m_pworker->moveToThread(m_pthread);
        qDebug()<< "start,thread id = " << QThread::currentThreadId();
    
        connect(m_pthread, &QThread::finished, m_pworker, &QObject::deleteLater);
        connect(this,SIGNAL(sig_dowork()),m_pworker,SLOT(slot_dowork()));
        connect(m_pworker,SIGNAL(sig_finish()),this,SLOT(slot_finish()));
    }
    
    MainWindow::~MainWindow()
    {
        qDebug()<<"~mainwindow()";
        delete ui;
        m_pthread->quit();
        m_pthread->wait();
    }
    
    void MainWindow::dowork()
    {
        m_pthread->start();
        emit sig_dowork();
    }
    
    void MainWindow::slot_finish()
    {
         qDebug()<< "finish,thread id = " << QThread::currentThreadId();
    }

    //main.cpp
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "tes.h"
    #include "mainwindow.h"
    
    int main(int argc, char *argv[])
    {
         QApplication a(argc, argv);
    
         MainWindow w;
         w.show();
         w.dowork();
    
        return a.exec();
    }

    /****************************************************************************

    1. 继承 QThread 重写 run 函数

    1

    2

    3

    4

    5

    6

    class Thread : public QThread

    {

        Q_OBJECT

    public:

        virtual void run() override;

    }

    1

    2

    3

    4

    void Thread::run()

    {

        ...

    }

    • 可调用 thread.start()启动线程,会自动调用 run 函数
    • 可调用 thread.isRunning()判断线程是否已启动
    • 可调用 thread.terminate()终止线程
    • 可调用 thread.wait()等待线程终止

    2. 继承 QObject 调用 moveToThread

    1

    2

    3

    4

    5

    6

    class Test : public QObject

    {

        Q_OBJECT

    public:

        void test();

    }

    1

    2

    3

    QThread th;

    Test test;

    test.moveToThread(&th);

    需要注意的是:此方法与继承 QThread 相比较,继承 QThread 只有 run 函数中的操作是在线程中执行的,而此方法中所有的成员函数都是在线程中执行

    3. 继承 QRunnable 重新 run 函数,结合 QThreadPool 实现线程池

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    #include

    #include

    #include

    #include

    #include

    class BPrint : public QRunnable

    {

        void run()

        {

            for ( int count = 0; count < 5; ++count )

            {

                qDebug() << QThread::currentThread();

                QThread::msleep(1000);

            }

        }

    };

    int main(int argc, char *argv[])

    {

        QCoreApplication a(argc, argv);

      

        QThreadPool threadpool;                 // 构建一个本地的线程池

        threadpool.setMaxThreadCount(3);        // 线程池中最大的线程数

         

        for ( int num = 0; num < 100; ++num )

        {

            BPrint *print;                      // 循环构建可在线程池中运行的任务

            threadpool.start(print);            // 线程池分配一个线程运行该任务

            QThread::msleep(1000);

        }

         

        return a.exec();

    }

    在上述例子当中,我们创建的 QRunnable 类型的指针 BPrint *print 是不需要我们手动去回收内存的,QThreadPool 在结束该任务的执行后会将对该内存进行清空

    有的小伙伴会有疑问,既然有 QThread 线程类了,为啥还要用 QRunnable + QThreadPool 创建线程池的方法来使用线程机制呢,感觉用起来很麻烦啊。所以这里要说明一下此方法的使用场景,当线程任务量非常大的时候,如果频繁的创建和释放 QThread 会带来非常大的内存开销,而线程池则可以有效避免这个问题

    还有一个问题需要注意一下,QThread 是集成自 QObject 的,我们通常会使用信号槽与外界进行通信。而 QRunnable 并不是继承自 QObject 类的,所以他无法使用信号槽机制进行通信。这里推荐两种方法,一个是使用 QMetaObject::invokeMethod()函数。另一个是使用多重继承的方法,自定义类需要同时继承自 QRunnable 和 QObject

    4. 使用 C++ 11 中的 sth::thread

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    #include

    void threadfun1()

    {

        std::cout << "threadfun1 - 1\r\n" << std::endl;

        std::this_thread::sleep_for(std::chrono::seconds(1));

        std::cout << "threadfun1 - 2" << std::endl;

    }

    void threadfun2(int iParam, std::string sParam)

    {

        std::cout << "threadfun2 - 1" << std::endl;

        std::this_thread::sleep_for(std::chrono::seconds(5));

        std::cout << "threadfun2 - 2" << std::endl;

    }

    int main()

    {

        std::thread t1(threadfun1);

        std::thread t2(threadfun2, 10, "abc");

        t1.join();      // 等待线程 t1 执行完毕

        std::cout << "join" << std::endl;

        t2.detach();    // 将线程 t2 与主线程分离

        std::cout << "detach" << std::endl;

    }

    运行结果:

    threadfun1 - 1

    threadfun2 - 1

    threadfun1 - 2

    join

    detach

    根据输出结果可以得知,t1.join() 会等待t1线程退出后才继续往下执行,t2.detach() 并不会等待,detach字符输出后,主函数退出,threadfun2还未执行完成,但是在主线程退出后,t2的线程也被已经被强退出

    5. Qt QtConcurrent 之 Run 函数

    Concurrent 是并发的意思,QtConcurrent 是一个命名空间,提供了一些高级的 API,使得所写的程序可根据计算机的 CPU 核数,自动调整运行的线程数目。这意味着今后编写的应用程序将在未来部署在多核系统上时继续扩展

    函数原型如下:
    QFuture QtConcurrent::run(Function function, ...)
    QFuture QtConcurrent::run(QThreadPool *pool, Function function, ...)

    简单来说,QtConcurrent::run() 函数会在一个单独的线程中执行,并且该线程取自全局 QThreadPool,该函数的返回值通过 QFuture API 提供

    需要注意的是:
    1)该函数可能不会立即运行; 函数只有在线程可用时才会运行
    2)通过 QtConcurrent::run() 返回的 QFuture 不支持取消、暂停,返回的 QFuture 只能用于查询函数的运行/完成状态和返回值
    3) Qt Concurrent 已经从 QtCore 中移除并成为了一个独立的模块,所以想要使用 QtConcurrent 需要在 pro 文件中导入模块:
    QT += concurrent

    使用方式有以下几种:
    1)将函数运行在某一个线程中,需要使用 extern

    1

    2

    extern void func();

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

    2)向该函数传递参数

    1

    2

    3

    4

    5

    6

    extern void FuncWithArguments(int arg1, const QString &string);

    int integer = ...;

    QString string = ...;

    // 需要传递的参数,则跟在函数名之后,依次加入

    QFuture<void> future = QtConcurrent::run(FuncWithArguments, integer, string);

    3) 获取该函数的计算结果

    1

    2

    3

    4

    5

    6

    extern QString Func(const QByteArray &input);

    QByteArray byte_array = ...;

    QFuture future = QtConcurrent::run(func, byte_array);

    ...

    QString result = future.result();

    4)常量成员函数

    1

    2

    3

    4

    5

    QByteArray bytearray = "hello world";

    // 在一个单独的线程中,调用 QByteArray 的常量成员函数 split(),传递给 run() 函数的参数是 bytearray

    QFuture< QList > future = QtConcurrent::run(bytearray, &QByteArray::split, ',');

    ...

    QList result = future.result();

    5)非常量成员函数

    1

    2

    3

    4

    5

    QImage image = ...;

    // 在一个单独的线程中,调用 QImage 的非常量成员函数 invertPixels(),传递给 run() 函数的参数是 &image

    QFuture<void> future = QtConcurrent::run(&image, &QImage::invertPixels, QImage::InvertRgba);

    ...

    future.waitForFinished();

    6)Lambda 表达式

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    #include

    #include

    #include

    QThreadPool pool;

    QFuture<void> future = QtConcurrent::run(&pool, [&](QObject *receiver){

        cv::Mat mat = QYImageProcessing::convertQImageToMat(image);

        cv::Mat center = cv::imread("dynaPhase_center.png");

         

        dynaPhase_alive = QYImageProcessing::getDiffPoint(mat, center);

         

        // 根据三个点自适应模拟条纹

        cv::Mat ret = DynamicCarrier::DC_Adaptive_Simulation(dynaPhase_center, dynaPhase_alive, dynaPhase_align);

        ret = ret*255;

        ret.convertTo(ret, CV_8UC1);

        QImage adaptive = QYImageProcessing::convertMatToQImage(ret);

         

        QYAlignControl *align = static_cast(receiver);

        align->callQmlGetAlivePoint(adaptive, dynaPhase_alive.x, dynaPhase_alive.y);

    }, this);

  • 相关阅读:
    【学习总结】什么是弹性负载均衡? LB和ELB的区别
    133道Java面试题及答案(面试必看)
    2023-11-14 mysql-LOGICAL_CLOCK 并行复制原理及实现分析
    MYSQL之压测
    React UI组件库——如何快速实现antd的按需引入和自定义主题
    这两天的一些碎碎念
    cs224w_colab3_2023学习笔记
    vue 浅解watch cli computed props ref vue slot axios nexttick devtools说明使用
    在Go中过滤范型集合:性能回顾
    Redis知识-实战篇(4)
  • 原文地址:https://blog.csdn.net/u011555996/article/details/127736578