QT 中多线程实现方法总结

news2024/11/27 5:23:37

第一: 用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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/4074.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

html5 -- canvas使用(1)

canvas 设置canvas标签 添加宽高 默认单位为px <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport&…

荧光标记氨基酸:荧光标记DL-天门冬氨酸,荧光标记甘氨酸-DL-天冬氨酸,DL aspartic acid labeled

产品名称&#xff1a;荧光标记甘氨酸-DL-天冬氨酸&#xff0c;DL aspartic acid labeled 甘氨酸-DL-天冬氨酸是一种化学物质&#xff0c;化学式是C6H10N2O5&#xff0c;分子量是208.17。 DL-天门冬氨酸(DL-Asp)在医药方面有着重要的用途,可用于合成DL-天门冬氨酸钾镁盐(脉安定…

云原生之K8s—yaml文件

目录 一、K8S支持的文件格式 1、yaml和json的主要区别 二、YAML 2.1、查看API资源版本标签 2.2、编写资源配置清单 编写nginx-test.yaml资源配置清单 创建资源对象 查看创建的pod资源 创建资源对象 网页访问一下 K8S中的port概述 创建yaml文件模板 查看生成yaml格式…

【python的静态方法,classmethod方法和__call___魔法方法】

classmethod魔法方法和staticmethodstaticmethod&#xff0c;静态方法classmethod&#xff0c;绑定类方法__call__&#xff0c;可调用类类方法staticmethod&#xff0c;静态方法 在python中&#xff0c;使用静态方法可以实现不需要实例化对象的绑定就可以直接调用的函数&#…

Linux系统编程·进程概念

你好&#xff0c;我是安然无虞。 文章目录自学网站上文回顾进程控制块—PCB查看进程初识系统调用初始fork函数练习题自学网站 推荐给老铁们两款学习网站&#xff1a; 面试利器&算法学习&#xff1a;牛客网 风趣幽默的学人工智能&#xff1a;人工智能学习 首个付费专栏&…

添加滚动彩色提醒通知公告代码

分享一个动态的滚动多样化的彩色提醒通知公告&#xff0c;代码是自适应的&#xff0c;放在很多地方都可以用&#xff0c;在wordpress、emlog等建站cms中&#xff0c;都可以在自定义侧边栏中&#xff0c;用来网站、博客的美化也是非常不错的选择。 使用说明: wordpress&#xff…

网络编程04-UDP的广播、组播

目录 一、UDP广播通信 1、什么是广播 2、特点 3、广播地址 4、实现广播的过程&#xff08;一定是使用UDP协议&#xff09; 广播发送端 广播接收方 练习1&#xff1a; 把广播通信进行实现 发送端 接收端 二、UDP组播&#xff08;群聊&#xff09; 1、概念 2、组播特…

(最新版2022版)剑指offer之动态规划题解

&#xff08;最新版2022版&#xff09;剑指offer之动态规划题解[剑指 Offer 42. 连续子数组的最大和][剑指 Offer 47. 礼物的最大价值][剑指 Offer 46. 把数字翻译成字符串][剑指 Offer 48. 最长不含重复字符的子字符][剑指 Offer 48. 矩形覆盖][剑指 Offer 买卖股票的最好时机…

小侃设计模式(五)-建造者模式与模板方法模式

1.概述 建造者模式&#xff08;Builder Pattern&#xff09;又叫生成器模式&#xff0c;是一种对象构建模式&#xff0c;它可以将复杂对象的建造过程抽象出来&#xff08;抽象类别&#xff09;&#xff0c;这个抽象过程的不同实现方法可以构造出不同表现&#xff08;属性&…

家庭主妇问题

一 问题描述 X 村的人们住在美丽的小屋里。若两个小屋通过双向道路连接&#xff0c;则可以说这两个小屋直接相连。X 村非常特别&#xff0c;可以从任意小屋到达任意其他小屋&#xff0c;每两个小屋之间的路线都是唯一的。温迪的孩子喜欢去找其他孩子玩&#xff0c;然后打电话给…

C++中TCP socket传输文件

在两个文件中都定义文件头和用到的宏&#xff1a; #define MAX_SIZE 10 #define ONE_PAGE 4096 struct FileHead {char str[260];int size; }; 在客户端发送接收阶段&#xff1a; //1.发送文件头char path[260] {0};cout<<"请输入文件路径"<<endl;cin…

数字图像处理MATLAB

数字图像处理MATLAB 基&#xff08;本&#xff09;操&#xff08;作&#xff09; 图片读取 Aimread(test.bmp); imshow(A);2. 图像写入 Aimread(test.bmp); imwrite(A,test-bak.bmp); Bimread(test-bak.bmp); imshow(B);3. 图像文件信息查询 infoimfinfo(test.bmp);4. 显示…

【创建型】生成器模式(Builder)

目录生成器模式(Builder)适用场景生成器模式实例代码&#xff08;Java&#xff09;生成器模式(Builder) 将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 适用场景 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方…

【SpringBoot笔记22】SpringBoot框架集成Redis数据库

这篇文章&#xff0c;主要介绍SpringBoot框架如何集成Redis数据库。 目录 一、SpringBoot集成Redis 1.1、引入依赖 1.2、配置redis连接信息 1.3、添加RedisTemplate配置类 1.4、编写测试类 1.5、运行测试 一、SpringBoot集成Redis Redis是一个非关系型数据库&#xff0c…

PCIe ECAM机制访问PCIE的配置空间

1.PCIe ECAM机制 PCI Express Enhanced Configuration Access Mechanism (ECAM)是访问PCIe配置空间的一种机制。是将PCIe的配置空间映射到MEM空间&#xff0c;使用MEM访问其配置空间的一种实现。可参考NCB-PCI_Express_Base_5.0r1.0-2019-05-22.pdf的第7.2.2小节。 其地址映射…

上海亚商投顾:沪指录得6连阳 两市成交再度破万亿

上海亚商投顾前言&#xff1a;无惧大盘大跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 三大指数今日横盘震荡&#xff0c;收盘集体小幅上扬&#xff0c;日K线均录得6连阳。虚拟现实概念股集体拉升&#…

【SA-Token】授权 鉴权中心微服务

授权 鉴权中心微服务 1 什么是JWT 1.2 JWT 的基本概念 1.3 JSON Web Token jwt 是一个开放标准 它定义了一种紧凑的、自包含的方式 用于作为JSON 对象在各方之间安全地传输信息 1.4.那些场景下可以考虑使用JWT &#xff1f; ​ 1.用户授权 信息交换 1.5 JWT的结构及其含义 …

镜频抑制滤波器对射频接收前端输出噪声的影响

射频接收前端包括LNA、Filter、Mixer等部件&#xff0c;从噪声因子级联的角度讲&#xff0c;希望接收链路第一级为高增益、低噪声系数放大器&#xff0c;以期望得到较低的系统噪声系数&#xff0c;提高接收灵敏度。除LNA外&#xff0c;接收链路还有一个关键的部件——镜频抑制滤…

精读大型网站架构:前端架构模块化的方法及困境,自研框架Trick

模块化的方法 网页和网页之间有很多相似或者相同的模块&#xff0c;模块化就是把这些模块抽离并独立管理。而模块化的方法&#xff0c;就是把模块的HTML、CSS和JavaScript文件独立出来&#xff0c;然后通过某种方法关联到使用这些模块的网页上。 在介绍模块化的具体方法之前&…

consul--基础--05--api

consul–基础–05–api 1、介绍 主要接口是RESTful HTTP API&#xff0c;该API可以用来增删查改nodes、services、checks、configguration。所有的endpoints主要分为以下类别 kv&#xff1a;Key/Value存储agent&#xff1a;Agent控制catalog&#xff1a;管理nodes和serviceshe…