QT 中多线程实现方法总结

2023-05-16

第一: 用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 &parameter) {
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()"<<endl;
}

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


 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 <QString>

#include <QDebug>

#include <QThread>

#include <QApplication>

#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<QString> 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<QList<QByteArray> > future = QtConcurrent::run(bytearray, &QByteArray::split, ',');

...

QList<QByteArray> 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<void> f1 =QtConcurrent::run(func,QString("aaa"));

//调用类成员函数 QFuture<void> 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 <QThread>

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 <QDebug>

MyThread::MyThread()
{

}

MyThread::~MyThread()
{

}

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

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


#include "mythread.h"
#include <QApplication>


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 <QObject>

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

signals:

public slots:
    void doWork();
};

#endif // MYWORK_H

#include "mywork.h"
#include <QDebug>
#include <QThread>

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

}

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

}

#include "mywork.h"
#include <QApplication>
#include <QThread>

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()"<<endl;
}
 
void Runnable::run()
{
       cout<<"Runnable::run()thread :"<<QThread::currentThreadId()<<endl;
}
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 <QApplication>
#include <QThread>
#include <QtConcurrent/QtConcurrent>

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

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 <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <QString>
#include <QObject>

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 <QMainWindow>
#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 <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <QString>
#include <QObject>
#include <QApplication>
#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 <QObject>

#include <QRunnable>

#include <QThread>

#include <QThreadPool>

#include <QDebug>

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 <thread>

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<T> QtConcurrent::run(Function function, ...)
QFuture<T> 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<QString> 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<QByteArray> > future = QtConcurrent::run(bytearray, &QByteArray::split, ',');

...

QList<QByteArray> 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 <QFuture>

#include <QtConcurrent>

#include <QThreadPool>

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<QYAlignControl *>(receiver);

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

}, this);

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QT 中多线程实现方法总结 的相关文章

随机推荐

  • c#中button单击时怎么消除高亮显示

    可以在button的click事件中加上移除焦点的方法如下 xff1a button clicked 43 61 new eventhandler object ob eventargs e 61 gt button BackColor 6
  • c#中控件事件注册的几种方法

    1 在子控件类中重写 xff0c 加入如下方法和事件成员后就可通过UI界面在事件编辑器中的杂项中找到 MyButton ClickEvent事件单击后设计器会自动添加一个方法绑定到MyButton ClickEvent事件中 public
  • c#中动态加载及显示UserControl和Form在父控件中

    1 UserControl的动态加载及显示与普通Control一致 如UserControl A 61 new UserControl parentControl Controls Add A 即可显示 2 Form的话需要show 或Vi
  • MFC中的模态对话框与非模态对话框的消息循环及消息传递问题(都可以通过SendMessage传递消息)

    注意 xff1a 非模态和模态对话框都有自己的消息响应过程函数WindowPro 只不过他们的调用模态是自己的消息泵 xff0c 非模态是由父窗体或父窗体的上一级的消息泵来调用 模态对话框和非模态对话框都可以通过SendMessage这个A
  • c#中子控件在父控件中显示的位置问题

    1 Control ShowDialog Control Parent 之前使Control StartPosition 61 FormStartPosition CenterScreen便可在父控件中居中于屏幕显示
  • 控件中的Cliked事件和MouseDown事件执行优先级问题

    MFC中 1 Button控件鼠标事件执行顺序 1 WM LBUTTONDOWN 2 WM LBUTTONUP 3 OnBnClickedButton1 2 Picture Control的鼠标事件执行顺序 1 WM LBUTTONDOWN
  • MFC中手动添加消息处理函数PreTranslateMessage截获按键等消息

    PreTranslateMessage作用和用法 PreTranslateMessage是消息在送给TranslateMessage函数之前被调用的 xff0c 绝大多数本窗体的消息都要通过这里 xff0c 比較经常使用 xff0c 当须要
  • OpenStack核心组件

    OpenStack覆盖了网络 虚拟化 操作系统 服务器等各个方面 它是一个正在开发中的云计算平台项目 xff0c 根据成熟及重要程度的不同 xff0c 被分解成核心项目 孵化项目 xff0c 以及支持项目和相关项目 每个项目都有自己的委员会
  • mfc中钩子的使用

    钩子的安装与卸载 系统是通过调用位于钩子链表最开始处的钩子函数而进行消息拦截处理的 xff0c 因此在设置钩子时要把回调函数放置于钩子链表的链首 xff0c 操作系统会使其首先被调用 由函数SetWindowsHookEx 负责将回调函数放
  • mfc-钩子的使用方法详解

    钩子的安装与卸载 系统是通过调用位于钩子链表最开始处的钩子函数而进行消息拦截处理的 xff0c 因此在设置钩子时要把回调函数放置于钩子链表的链首 xff0c 操作系统会使其首先被调用 由函数SetWindowsHookEx 负责将回调函数放
  • mfc-钩子的使用方法详解

    钩子的安装与卸载 系统是通过调用位于钩子链表最开始处的钩子函数而进行消息拦截处理的 xff0c 因此在设置钩子时要把回调函数放置于钩子链表的链首 xff0c 操作系统会使其首先被调用 由函数SetWindowsHookEx 负责将回调函数放
  • c#中的消息处理函数和vc中的消息处理函数区别

    从vc入门编程的 xff0c 相信大家都很熟悉PreTranslateMessage和WindowProc两个函数 xff0c 前者是预处理windows发给控件的消息 xff0c 后者是处理剩余的控件消息 对于PreTranslateMe
  • c#中怎么截获按钮消息的几种方法实例验证

    public partial class Form2 Form public Form2 InitializeComponent this button1 MouseEnter 43 61 new EventHandler object o
  • QT中连接Mysql数据库及增删改查实例

    QT连接Mysql数据库步骤相对比较麻烦 xff0c 因此写了篇文章将详细过程呈现给大家 xff0c 欢迎大家来评论区指导提问 方法一 直接通过MySQL的驱动加载数据库 示例代码 xff1a xff08 1 xff09 在 pro文件中添
  • mysql 大批量数据优化插入数据速度

    在 MySQL 中 xff0c 向数据表插入数据时 xff0c 索引 唯一性检查 数据大小是影响插入速度的主要因素 本节将介绍优化插入数据速度的几种方法 根据不同情况 xff0c 可以分别进行优化 对于 MyISAM 引擎的表 xff0c
  • qt连接MySql数据库及增删改查示例

    include 34 mainwindow h 34 include 34 ui mainwindow h 34 include 34 QtSql QSqlDatabase 34 include 34 QMessageBox 34 incl
  • QT中拷贝一个项目的ui界面到另一个项目时Ui.h文件时Ui.h文件控件变量错乱问题解决

    qt中赋值另一个项目的ui时可能会产生在UI h的界面文件中控件变量确实或错乱 xff0c 这是可以去另一个项目的UI h文件中赋值全部代码拷贝到错乱的UI h中 xff0c 然后修改 一下 void setupUi QDialog Ins
  • C++中TCP socket传输文件

    在两个文件中都定义文件头和用到的宏 xff1a define MAX SIZE 10 define ONE PAGE 4096 struct FileHead char str 260 int size 在客户端发送接收阶段 xff1a 1
  • 使用Zeromq和protobuf实现的socket通信

    本文介绍使用ZeroMQ 下文简称ZMQ 结合protobuf序列化实现客户端和服务端的通信 在之前的一篇文章中 http blog csdn net cjf wei article details 52894560 介绍了Google的p
  • QT 中多线程实现方法总结

    第一 xff1a 用QtConcurrentRun类 xff0c 适合在另一个线程中运行一个函数 不用继承类 xff0c 很方便 第二 xff1a 用QRunnable和QThreadPool结合 继承QRunnable xff0c 重写r