赞
踩
使用多线程,一般有两种方式,然后我又发现了几种,一共不下于四种:
继承自QThread类,然后重写run()函数。
这种方法,只有run()内部才是多线程,其他的所有函数,都属于主线程。
适合功能非常简单。或者高频触发的功能。
继承自QObject类,然后使用moveToThread()移到子线程,通过信号关联触发。
这种方法,必须通过信号触发才行。信号关联的那个槽函数是子线程,内部调用的也是子线程。
非常牛逼的写法,虽然也有坑,但是惊喜一样很多,甚至可以共享资源。
QMetaObject::invokeMethod可以快速触发,而不用非使用信号触发。
适合开少量底层自己跑的单独线程,并且这个线程可以和主线程共享资源。
使用QtConcurrent 并发模块。QtConcurrent::run()来调用此函数的方法,一样可以开启单独线程。
适合在一个类中有很多想单独运行的线程且没有返回值的场合。
如果想获取返回值,还是需要信号关联,直接使用reslut获取会阻塞主线程。
使用此方法,我发现工作类中,如果你新写了一个函数,那么你放到哪里,就变成了哪个线程,非常方便,并且不同线程之间可以实现一定程度的资源共享。但同时,也有一些坑,比如不能使用普通的connect,需要引用,或者new等情况,各位也多试试吧
使用QRunnable线程池,重写run函数,使用QThreadPool::globalInstance()->start() 调用。
一般来说,一个任务就是一个类。可以使用QThreadPool::globalInstance()进行管理。
为图方便,也可以在一个work里面设置变量,然后用初始化的变量来控制执行某个函数。
先说结论:最好用的是moveToThread 和 QtConcurrent::run();
1.QRunnable
QRunnable是一个轻量级的线程任务对象,适合处理一些相对简单的任务。可以将QRunnable放入QThreadPool中,由线程池自动管理线程,并发执行多个任务。QRunnable的优点是简单易用、轻量级,可以很好地利用现有的线程池资源,提高程序的并发性能。同时,QRunnable还支持取消任务、传递参数、返回值等功能,非常灵活。QRunnable的缺点是无法控制线程的创建和销毁,也无法对线程进行更细粒度的控制,适合处理大量的并行任务,但不适合处理长时间运行的任务。
当然,不适合长时间运行是官方说的,我自己测试的长时间运行好像没问题。
2.QtConcurrent
QtConcurrent提供了一组函数,如QtConcurrent::run()
、QtConcurrent::mapped()
等,可以方便地创建并发任务。利用并发算法来自动管理线程的创建和销毁,无需手动管理线程。QtConcurrent的优点是简单易用、封装了线程池等底层细节,可以方便地实现并行计算或数据处理任务。同时,QtConcurrent还支持取消任务、传递参数、返回值等功能,非常灵活。QtConcurrent的缺点是无法对线程进行更细粒度的控制,也无法处理长时间运行的任务,因为QtConcurrent会自动终止超时的任务。
发现使用QtConcurrent::run()超级简单的有没有!!!
3.QThread
QThread是一个底层的多线程API,可以进行更细粒度的线程控制。通过继承QThread类并重写其run()方法来实现自定义线程逻辑。QThread的优点是可以进行更细粒度的线程控制,如线程的优先级、睡眠、唤醒等;同时还可以处理长时间运行的任务,可以对线程进行更细粒度的管理和控制。QThread的缺点是需要手动管理线程的创建和销毁,比较繁琐,同时也容易产生内存泄漏或线程死锁等问题。
老方法,之前没写在run()里面的,统统不是子线程,而且总会有泄露问题,我觉得非大佬少用
4.moveToThread()
moveToThread()是QObject类中提供的一个函数,用于将一个QObject对象从当前线程移动到另一个线程中执行。moveToThread()的优点是可以处理一些长时间运行的任务、网络操作、IO操作、后台数据处理等,避免阻塞主线程,同时也可以方便地与其他QObject对象进行交互,使用信号槽机制、事件处理机制或直接调用对象的方法。moveToThread()的缺点是需要手动管理线程的创建和销毁,同时也容易产生内存泄漏或线程死锁等问题。
综上所述,QRunnable适合处理大量的并行任务,但不适合处理长时间运行的任务;QtConcurrent适合处理并行计算或数据处理任务,但不适合处理长时间运行的任务;QThread适合处理需要更细粒度线程控制或涉及到复杂多线程逻辑的任务,且可以处理长时间运行的任务;而moveToThread()适合处理长时间运行的任务、网络操作、IO操作、后台数据处理等。在实际应用中,应根据具体的需求选择合适的多线程编程工具。
我觉得这个是进阶的人经常用的方法,但是必须emit触发开始,直接调用还是主线程!!!
以下是方法的示例:
- class MainWindow : public QMainWindow
- {
- Q_OBJECT
-
- public:
- MainWindow(QWidget *parent = nullptr);
- ~MainWindow();
-
- private:
- Ui::MainWindow *ui;
-
- MyThread myThread;
- };
-
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- , ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
- qDebug() << "MainUI()" << "thread:" << QThread::currentThreadId();
-
- myThread.start();
-
- connect(&myThread,&MyThread::workFinish,this,[=](){
- qDebug()<<"OK";
- });
- }
-
- MainWindow::~MainWindow()
- {
- delete ui;
- }
- #include <QObject>
- #include <QThread>
- #include <QDebug>
-
- class MyThread : public QThread
- {
- Q_OBJECT
- public:
- explicit MyThread(QObject *parent = nullptr);
- ~MyThread();
- void run() override;
-
- signals:
- void workFinish();
-
- private:
- int num = 10;
- };
-
- // ***************************************************
-
- #include "myThread.h"
- MyThread::MyThread(QObject *parent) : QThread(parent)
- {
- qDebug() << "MyThread()" << "thread:" << QThread::currentThreadId();
- }
-
- MyThread::~MyThread()
- {
- qDebug() << "MyThread()~" << "thread:" << QThread::currentThreadId();
- }
-
- void MyThread::run()
- {
- qDebug() << "run()" << "thread:" << QThread::currentThreadId();
- while(num--){
- qDebug()<<"run:"<<num;
- QThread::sleep(1);
- }
- emit workFinish();
- }
MyThread() thread: 0x3d38 MainUI() thread: 0x3d38 run() thread: 0x2f04 run: 9 run: 8 run: 7 run: 6 run: 5 run: 4 run: 3 run: 2 run: 1 run: 0 OK MyThread()~ thread: 0x3d38
- // 需要调用那个线程工作的控制类:
- class MainWindow : public QMainWindow
- {
- Q_OBJECT
-
- public:
- MainWindow(QWidget *parent = nullptr);
- ~MainWindow();
-
- // 重点1:写触发信号:
- signals:
- void doSomething(const QString &cmd);
- void doThis();
-
- // 重点2:线程和工作对象,不要直接在构造函数创建
- private:
- Ui::MainWindow *ui;
- QThread myWorkThread;
- MyWorker myWorker;
-
- };
-
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- , ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
-
- myWorker.moveToThread(&myWorkThread);
- myWorkThread.start();
-
- // 方式一:直接调用,还是主线程
- // myWorker.doSomething("test");
-
- // 方式二:信号关联
- // 带参数:
- connect(this,&MainWindow::doSomething,&myWorker,&MyWorker::doSomething);
- emit doSomething("test");
-
- // 不带参数:
- connect(this,&MainWindow::doThis,&myWorker,&MyWorker::doThis);
- emit doThis();
-
- // 注意,这两个信号虽然几乎同时触发,但是因为在一个线程中,
- // 所以有前后关系,必须doSomething完成,才可以去完成doThis
-
- // 获取结束信号:
- connect(&myWorker,&MyWorker::resultNotify,this,[=](){
- qDebug()<<"doSomething finished!!!";
- });
- }
-
- MainWindow::~MainWindow()
- {
- myWorkThread.quit();
- delete ui;
- }
-
- #include <QObject>
- #include <QDebug>
- #include <QThread>
-
- class MyWorker : public QObject
- {
- Q_OBJECT
- public:
- explicit MyWorker(QObject *parent = nullptr);
- ~MyWorker();
- private:
- void func(const QString& cmd);
- public slots:
- void doSomething(const QString& cmd);
- void doThis();
- signals:
- void resultNotify(const QString& des); // 结束信号
- private:
- bool flag = true;
- int num = 10;
- };
-
- // ***************************************
-
- MyWorker::MyWorker(QObject *parent) : QObject(parent)
- {
- qDebug() << "Worker()" << "thread:" << QThread::currentThreadId();
- }
-
- MyWorker::~MyWorker()
- {
- qDebug() << "~Worker()" << "thread:" << QThread::currentThreadId();
- }
-
- void MyWorker::func(const QString &cmd = "test")
- {
- qDebug() << "func()" << "thread:" << QThread::currentThreadId();
- while (num--) {
- qDebug()<<"func:"<<cmd;
- QThread::sleep(1);
- }
- emit resultNotify(cmd);
- }
-
- void MyWorker::doSomething(const QString &cmd)
- {
- qDebug() << "doSomething()" << "thread:" << QThread::currentThreadId();
- flag = true;
- func(cmd);
- }
-
- void MyWorker::doThis()
- {
- qDebug() << "doThis()" << "thread:" << QThread::currentThreadId();
- int n=10;
- while (n--) {
- qDebug()<<"doThis:";
- QThread::sleep(1);
- }
- }
-
Worker() thread: 0x33a0 doSomething() thread: 0x2740 func() thread: 0x2740 func: "test" func: "test" func: "test" func: "test" func: "test" func: "test" func: "test" func: "test" func: "test" func: "test" doThis() thread: 0x2740 doThis: doSomething finished!!! doThis: doThis: doThis: doThis: doThis: doThis: doThis: doThis: doThis: ~Worker() thread: 0x33a0
- myThread = new QThread();
- myworker = new Worker();
- myThread->start();
-
- //myworker.init(); // 还是主线程,别用
-
- // 一般的调用方法:
- connect(this,&MainWindow::doInit,myworker,&Worker::init);
- emit doInit();
-
- // 简化写法:
- QMetaObject::invokeMethod(myworker,"init");
有的版本可能需要在pro中,添加 QT += concurrent ,未测试。我发现我不加也能用。
调用方式:
1. QFuture<void> future = QtConcurrent::run(&worker, &Worker::doWork);
2. QtConcurrent::run([=](){ worker->UDPtest("直接调用即可"); });
3. run很多写法,用法不固定,我喜欢方法2,其他写法可自行百度。
- #ifndef WORKER_H
- #define WORKER_H
-
- #include <QObject>
- #include <QThread>
- #include <QDebug>
- #include <QtCore>
- #include <QtConcurrent/QtConcurrent>
-
- class Worker : public QObject
- {
- Q_OBJECT
- public:
- explicit Worker(QObject *parent = nullptr);
- Q_INVOKABLE void init(); // 类中自己调用
- Q_INVOKABLE void doWork(); // 外部调用,无返回值
- Q_INVOKABLE int doSomething(); // 外部调用,有返回值
- signals:
- void getResult(int value);
- };
- #endif // WORKER_H
-
- // ***********************************************************
-
- #include "worker.h"
- Worker::Worker(QObject *parent) : QObject(parent)
- {
- // 自己内部调用
- QFuture<void> future = QtConcurrent::run(this, &Worker::init);
- }
- // 内部调用
- void Worker::init()
- {
- qDebug() << "init()" << "thread:" << QThread::currentThreadId();
-
- int num = 8;
- while (num--) {
- qDebug()<<"init: "<<num;
- QThread::sleep(1);
- }
- }
- // 没有返回值的函数:
- void Worker::doWork()
- {
- qDebug() << "doWork()" << "thread:" << QThread::currentThreadId();
-
- int num = 10;
- while (num--) {
- qDebug()<<"doWork: "<<num;
- QThread::sleep(1);
- }
- }
- // 有返回值的函数:
- int Worker::doSomething()
- {
- qDebug() << "doSomething()" << "thread:" << QThread::currentThreadId();
-
- int n = 5;
- while (n--) {
- qDebug()<<"doSomething: "<<n;
- QThread::sleep(1);
- }
- emit getResult(0);
- return 0;
- }
- #include <QMainWindow>
- #include <worker.h>
- #include <QThread>
- #include <QtCore>
- #include <QtConcurrent/QtConcurrent>
-
- class MainWindow : public QMainWindow
- {
- Q_OBJECT
- public:
- MainWindow(QWidget *parent = nullptr);
- ~MainWindow(){};
- void doWork();
- void doSomething();
-
- private:
- Ui::MainWindow *ui;
- Worker worker;
-
- };
- #endif // MAINWINDOW_H
-
- // *************************************************************
- #include "mainwindow.h"
-
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- {
- ui->setupUi(this);
- qDebug() << "QMainWindow()" << "thread:" << QThread::currentThreadId();
- doWork();
- doSomething();
- }
- // 调用无返回值的函数
- void MainWindow::doWork()
- {
- // 将函数运行在子线程中
- QFuture<void> future = QtConcurrent::run(&worker, &Worker::doWork);
- }
-
- // 调用有返回值的函数:
- void MainWindow::doSomething()
- {
- // 将函数运行在子线程中
- QFuture<int> future = QtConcurrent::run(&worker, &Worker::doSomething);
-
- // 通过result来获取结果,会直接阻塞主线程
- // int res = future.result();
- // qDebug()<<"res:"<<res;
-
- // 使用信号关联,就不会阻塞主线程
- connect(&worker,&Worker::getResult,this,[=](int res){
- qDebug()<<"res:"<<res;
- });
- }
-
init() thread: 0x3a88 init: 7 QMainWindow() thread: 0x2108 doWork() thread: 0x1d78 doSomething() thread: 0x1f00 doWork: 9 doSomething: 4 init: 6 doSomething: 3 doWork: 8 init: 5 doSomething: 2 doWork: 7 init: 4 doSomething: 1 doWork: 6 init: 3 doWork: 5 doSomething: 0 init: 2 doWork: 4 res: 0 init: 1 doWork: 3 init: 0 doWork: 2 doWork: 1 doWork: 0
可以看到,一共四个线程。
// 直接在其他线程中,调用即可。也可以传参。但是这个函数最好是解耦的,就是不要有太多关联: // 但是如果内部有信号关联会有很多坑,需要加QEvenloop,和暂停信号。 QtConcurrent::run([=](){ worker->UDPtest("UDP test"); });
- class Worker : public QObject
- {
- Q_OBJECT
- public:
- //static Worker *instance;
- //static Worker * getInstance();
-
- explicit Worker(QObject *parent = nullptr);
- ~Worker();
- void init(); // 类中自己调用
- void doWork(); // 外部调用,无返回值
- int doSomething(); // 外部调用,有返回值
- void test(const QString msg);
- void UDPtest(const QString msg);
- signals:
- void getResult(int value);
- void setEnd();
-
- private slots:
- void processPendingDatagrams_com1();
-
- private:
- QUdpSocket *udpSocket_Com1;
- bool thisFlag = true;
- };
-
- // ****************************
-
- #include "worker.h"
- //Worker* Worker::instance = nullptr;
- Worker::Worker(QObject *parent) : QObject(parent)
- {
- // 自己内部调用
- QFuture<void> future = QtConcurrent::run(this, &Worker::init);
- }
-
- Worker::~Worker()
- {
- thisFlag = false;
- emit setEnd();
- }
-
- //Worker *Worker::getInstance()
- //{
- // if(instance == nullptr){
- // instance = new Worker();
- // }
- // return instance;
- //}
-
- void Worker::init()
- {
- qDebug() << "init()" << "thread:" << QThread::currentThreadId();
-
- int num = 8;
- while (num--) {
- qDebug()<<"init: "<<num;
- QThread::sleep(1);
- }
- }
-
- void Worker::doWork()
- {
- qDebug() << "doWork()" << "thread:" << QThread::currentThreadId();
-
- int num = 10;
- while (num--) {
- qDebug()<<"doWork: "<<num;
- QThread::sleep(1);
- }
- }
-
- int Worker::doSomething()
- {
- qDebug() << "doSomething()" << "thread:" << QThread::currentThreadId();
-
- int n = 5;
- while (n--) {
- qDebug()<<"doSomething: "<<n;
- QThread::sleep(1);
- }
- emit getResult(0);
- return 0;
- }
-
- void Worker::test(const QString msg)
- {
- qDebug() << "test()" << "thread:" << QThread::currentThreadId();
- int n = 3;
- while (n--) {
- qDebug()<<"test: "<<msg;
- QThread::sleep(1);
- }
- }
-
- void Worker::UDPtest(const QString msg)
- {
- QEventLoop loop;
- // 如果不加暂停信号,很多线程的loop会无法停止。
- connect(this,&Worker::setEnd,[&](){
- loop.quit();
- });
-
- qDebug() << "UDPtest()" << "thread:" << QThread::currentThreadId();
- udpSocket_Com1 = new QUdpSocket();
- udpSocket_Com1->bind(QHostAddress::AnyIPv4, 8002);
-
- // 方法一:加QEventLoop
- connect(udpSocket_Com1, &QIODevice::readyRead, new Worker,&Worker::processPendingDatagrams_com1);
-
- // 方法二:加QEventLoop
- // connect(udpSocket_Com1, &QIODevice::readyRead, [=](){
- // while (udpSocket_Com1->hasPendingDatagrams()) {
- // QByteArray datagram;
- // datagram.resize(udpSocket_Com1->pendingDatagramSize());
- // QHostAddress sender;
- // quint16 senderPort;
- // udpSocket_Com1->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
- // qDebug() << "Received udp datagram: " << QString(datagram);
- // udpSocket_Com1->writeDatagram(msg.toUtf8(), QHostAddress("127.0.0.1"), 8001);
- // }
- // });
- //if(!thisFlag) loop.quit();
- loop.exec();
-
- // 方法三:可以不加QEventLoop。但是要加循环,并且要用flag控制退出。
- // while (flag) {
- // while (udpSocket_Com1->hasPendingDatagrams()) {
- // QByteArray datagram;
- // datagram.resize(udpSocket_Com1->pendingDatagramSize());
- // QHostAddress sender;
- // quint16 senderPort;
- // udpSocket_Com1->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
- // qDebug() << "Received udp datagram: " << QString(datagram);
- // udpSocket_Com1->writeDatagram(msg.toUtf8(), QHostAddress("127.0.0.1"), 8001);
- // }
- // }
- }
-
- void Worker::processPendingDatagrams_com1()
- {
- //qDebug("processPendingDatagrams_com1.....");
- QUdpSocket* udpSocket = qobject_cast<QUdpSocket*>(sender());
- if(!udpSocket) return;
-
- //qDebug("udpSocket.....");
- while (udpSocket->hasPendingDatagrams()) {
- QByteArray datagram;
- datagram.resize(udpSocket->pendingDatagramSize());
- QHostAddress sender;
- quint16 senderPort;
- udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
- qDebug() << "Received udp datagram: " << QString(datagram);
- }
- }
QRunnable是一个轻量级的线程任务对象,适合处理一些相对简单的任务。可以将QRunnable放入QThreadPool中,由线程池自动管理线程,并发执行多个任务。QRunnable的优点是简单易用、轻量级,可以很好地利用现有的线程池资源,提高程序的并发性能。
要点:
如果需要信号传递,就需要继承自QObject
传参一般通过构造函数传参
如果是长时间任务,可以通过添加flag来控制停止
- #ifndef WORKER_H
- #define WORKER_H
-
- #include <QObject>
- #include <QRunnable>
- #include <QDebug>
- #include <QThread>
-
- class Worker : public QObject,public QRunnable
- {
- Q_OBJECT
- public:
- explicit Worker(int num = 0, QObject *parent = nullptr);
- void run() override;
- void dowork_0(); // 简单任务,不传参
- void dowork_1(); // 简单任务,传参
- void dowork_2(); // 复杂长时间任务,无返回值
- void dowork_3(); // 复杂长时间任务,有返回值
- bool flag = true;
- signals:
- void work_finished();
-
- private:
- int n = 0;
- };
-
- #endif // WORKER_H
-
- // **************
- #include "worker.h"
-
- Worker::Worker(int num, QObject *parent) : QObject(parent)
- {
- n = num;
- }
-
- void Worker::run()
- {
- switch (n) {
- case 0:
- dowork_0();
- break;
- case 1:
- dowork_1();
- break;
- case 2:
- dowork_2();
- break;
- default:
- dowork_3();
- break;
- }
- }
-
- void Worker::dowork_0()
- {
- qDebug() << "dowork_0()" << "thread:" << QThread::currentThreadId();
- int nn = 3;
- while (nn--) {
- n++;
- qDebug()<<"dowork_0: "<<nn<<" n:"<<n;
- QThread::sleep(1);
- }
- emit work_finished();
- }
-
- void Worker::dowork_1()
- {
- qDebug() << "dowork_1()" << "thread:" << QThread::currentThreadId();
- int nn = 5;
- while (nn--) {
- n++;
- qDebug()<<"dowork_1: "<<nn<<" n:"<<n;
- QThread::sleep(1);
- }
- emit work_finished();
- }
-
- void Worker::dowork_2()
- {
- qDebug() << "dowork_2()" << "thread:" << QThread::currentThreadId();
- int nn = 7;
- while (flag && nn--) {
- n++;
- qDebug()<<"dowork_2: "<<nn<<" n:"<<n;
- QThread::sleep(1);
- }
- emit work_finished();
- }
-
- void Worker::dowork_3()
- {
- qDebug() << "dowork_3()" << "thread:" << QThread::currentThreadId();
- int nn = 1;
- while (flag && nn++) {
- n++;
- qDebug()<<"dowork_3: "<<nn<<" n:"<<n;
- QThread::sleep(1);
- }
- emit work_finished();
- }
// 注意别写构造函数里面: Worker *work_0 = new Worker(0); Worker *work_1 = new Worker(1); Worker *work_2 = new Worker(2); Worker *work_3 = new Worker(3); // 使用 QThreadPool::globalInstance()->start(work_0); QThreadPool::globalInstance()->start(work_1); QThreadPool::globalInstance()->start(work_2); QThreadPool::globalInstance()->start(work_3);
- // 关联:
- connect(work_0,&Worker::work_finished,this,[](){
- qDebug()<<"worker_0 finished";
- });
-
- connect(work_1,&Worker::work_finished,this,[](){
- qDebug()<<"worker_1 finished";
- });
-
- connect(work_2,&Worker::work_finished,this,[](){
- qDebug()<<"worker_2 finished";
- });
-
- connect(work_3,&Worker::work_finished,this,[](){
- qDebug()<<"worker_3 finished";
- });
-
- // 界面关闭可以控制底层线程关闭:
- MainWindow::~MainWindow()
- {
- work_0->flag = false;
- work_1->flag = false;
- work_2->flag = false;
- work_3->flag = false;
- delete ui;
- }
这里要注意!!!!!!!
这里要注意!!!!!!!
这里要注意!!!!!!!
使用QRunnable和QtConcurrent都是自动创建和销毁的,实在不行加个flag,不用担心资源浪费。
但是Qthread和movetoThread就需要手动管理停止。
- // 创建线程
- QThread* thread = new QThread;
- MyWorker* worker = new MyWorker; // 自定义的工作对象
- worker->moveToThread(thread); // 将工作对象移动到新创建的线程中
-
- // 连接信号和槽
- connect(thread, &QThread::started, worker, &MyWorker::doWork);
- connect(worker, &MyWorker::workFinished, thread, &QThread::quit);
- connect(thread, &QThread::finished, worker, &MyWorker::deleteLater);
- connect(thread, &QThread::finished, thread, &QThread::deleteLater);
-
- // 启动线程
- thread->start();
-
- // 销毁线程
- // 在合适的时机,如应用程序退出前
- thread->quit();
- thread->wait(); // 等待线程结束
- delete thread;
- // 创建线程
- QThread* thread = new QThread;
- MyObject* object = new MyObject; // 自定义的QObject对象
- object->moveToThread(thread); // 将对象移动到新创建的线程中
-
- // 连接信号和槽
- connect(thread, &QThread::started, object, &MyObject::onThreadStarted);
- connect(object, &MyObject::finished, thread, &QThread::quit);
- connect(thread, &QThread::finished, object, &MyObject::deleteLater);
- connect(thread, &QThread::finished, thread, &QThread::deleteLater);
-
- // 启动线程
- thread->start();
-
- // 销毁线程
- // 在合适的时机,如应用程序退出前
- thread->quit();
- thread->wait(); // 等待线程结束
- delete thread;
由打印结果可知,很多时候我们以为是子线程,其实并不是子线程,而且该关联的信号必须关联,重写run方法,那么只有run里面的才是子线程。
特别是很多人推荐使用的moveToThread,其实坑一样很多!!!如果是继承自QObject,即便moveToThread()以后,直接使用函数也还是主线程,必须使用信号关联触发才是子线程。关键是,如果你只创建了一个Qthread,这还不是异步操作的,只是单独开了一个子线程而已,并不是几个emit以后,立马创建了多个并行的子线程,并不是啊!你得多开几个QThread才行!
最开始只是想排查Bug, 但是经过我的测试,竟然发现还有 Qtconcurrent::run()的简单用法,只要注意其中的一些注意事项,就可以做到简单易用。然后再发现还有QRunnable的方法,于是写出来了这篇文章。其中Qtconcurrent::run()在解耦好的单独运行函数的时候超级好用!
但是在测试的过程中,发现了很多坑,例如资源共享,同步的问题,还有线程停止的问题!!!任何一种线程方法,如果不添加flag的话,很容易在后台运行而无法停止,因为在QT中,GUI线程和底层是分开的,如果界面点击了关闭,底层不控制关闭自然是停止不了的,容易出问题,所以最好长时间的任务都做信号或者一个flag来控制关闭。
最终总结:
最好用的是moveToThread 和 Qtconcurrent::run()
moveToThread 的最佳写法:
- //在控制的类头文件中写入
- QThread *myThread;
- Worker *myworker;
-
- //在构造函数中:
- myThread = new QThread();
- myworker = new Worker();
- myThread->start();
- myworker->moveToThread(myThread);
-
- ***************调用*****************
- // 主函数调用
- // myworker->init();
-
- // 信号触发
- //connect(this,&MainWindow::doInit,myworker,&Worker::init);
- //emit doInit();
-
- // 调用方式(信号触发):直接在子线程运行,而且不影响myworker.init()的使用
- QMetaObject::invokeMethod(myworker,"test"); // 子线程
- myworker->init(); // 还是主线程
-
- // 需要传参的时候
- // QMetaObject::invokeMethod(myworker, "mySlot", Qt::DirectConnection, Q_ARG(int, 42), Q_ARG(float, 100));
-
- *********************************工作类写法******************************************
- // 以上这种方式,只要Worker类中,在publit slots:下面有一个test函数就可以。
- class Worker : public QObject
- {
- Q_OBJECT
- public:
- explicit Worker(QObject *parent = nullptr);
-
- public slots:
- int init();
- void test();
- void mySlot(int ,float);
- signals:
-
- private:
- int nn = 0;
- };
-
- **************************************
- 【注意】:
- 使用此方法,看似是在一个类中,资源可以共享,实际上数据确实可以共享,比如那个nn,可是内部指针数据有些是无法正常跨线程使用的,比如UDPsocket和串口的,一般情况是都不能跨线程使用,会出问题。但是经过我的测试,加一个延时就解决了。
-
- // 信号触发的简单写法:
- QMetaObject::invokeMethod(myworker,"init"); // 内部写了UDPsocket的创建,bind和connect
- QThread::sleep(1);
- myworker->test(); // 内部写了UDPsocket的定时发送
Qtconcurrent::run()最佳写法:
- Qtconcurrent::run([&](){
- myworker.init();
- });
-
- // 此方法注意内部信号关联的问题。connect需要加eventloop
后来根据我的测试发现,跨线程使用的时候,有时候好像是不能跨线程共享资源,有时候又可以,于是我写了下面的代码,经过测试,在init下面加一个延时,就可以有效的实现跨线程共享资源,即便是UDPsocket一样可以实现:
工作类测试demo:
- // 工作类头文件:
- #ifndef WORKER_H
- #define WORKER_H
-
- #include <QObject>
- #include <QThread>
- #include <QDebug>
- #include <QUdpSocket>
-
- class Worker : public QObject
- {
- Q_OBJECT
- public:
- explicit Worker(QObject *parent = nullptr);
-
- public slots:
- int init();
- void test();
- signals:
-
- private:
- int nn = 0;
- QUdpSocket *udpSocket;
- };
-
- #endif // WORKER_H
-
- // 工作类cpp文件
- #include "worker.h"
-
- Worker::Worker(QObject *parent) : QObject(parent)
- {
- qDebug() << "Worker()" << "thread:" << QThread::currentThreadId();
- }
-
- int Worker::init()
- {
- qDebug() << "init()" << "thread:" << QThread::currentThreadId();
- udpSocket = new QUdpSocket();
- udpSocket->bind(QHostAddress::AnyIPv4, 8002);
- connect(udpSocket, &QIODevice::readyRead, [&](){
- // 因为是引用,仍然是创建socket的那个线程。
- qDebug() << "//" << "thread:" << QThread::currentThreadId();
- qDebug() << "UDPtest2 udpSocket_Com1 addr: "<< udpSocket;
- while (udpSocket->hasPendingDatagrams()) {
- QByteArray datagram;
- datagram.resize(udpSocket->pendingDatagramSize());
- QHostAddress sender;
- quint16 senderPort;
- udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
- qDebug() << "Received udp datagram: " << QString(datagram);
- udpSocket->writeDatagram(QString("haha").toUtf8(), QHostAddress("127.0.0.1"), 8001);
- }
- });
- return 0;
- }
-
- void Worker::test()
- {
- qDebug() << "test()" << "thread:" << QThread::currentThreadId();
- int num = 10;
- while (num--) {
- nn++;
- qDebug()<<"test nn:"<<nn;
- udpSocket->writeDatagram(QString("nn:%1 ").arg(nn).toUtf8(), QHostAddress("127.0.0.1"), 8001);
- QThread::sleep(1);
- }
- }
工作类使用Demo:
- // 类中:
- private:
- QThread *myThread;
- Worker *myworker;
-
- // 调用:
- myThread = new QThread();
- myworker = new Worker();
- myThread->start();
- myworker->moveToThread(myThread);
-
-
- // 信号触发的简单写法:开启子线程
- QMetaObject::invokeMethod(myworker,"init");
-
- // 重点:这里加延时就可以正常运行!!!不加就报错
- QThread::sleep(1);
-
- // 还是主线程运行:
- myworker->test();
打印结果:
MainWindow() thread: 0x35dc
Worker() thread: 0x35dc
init() thread: 0x28c
test() thread: 0x35dc
test nn: 1
// thread: 0x28c
UDPtest2 udpSocket_Com1 addr: QUdpSocket(0x2037ce65550)
Received udp datagram: "OUTP OFF"
test nn: 2
// thread: 0x28c
UDPtest2 udpSocket_Com1 addr: QUdpSocket(0x2037ce65550)
Received udp datagram: "OUTP OFF"
// thread: 0x28c
UDPtest2 udpSocket_Com1 addr: QUdpSocket(0x2037ce65550)
Received udp datagram: "OUTP OFF"
test nn: 3
test nn: 4
test nn: 5
test nn: 6
test nn: 7
test nn: 8
test nn: 9
test nn: 10
可以看出,不光变量可以跨线程共享,甚至连socket也可以跨线程共享。不得不说,moveToThread只要用好了是非常好用的。甚至我发现QThread如果是指针,而不是变量,即便不处理线程销毁和设置停止位,一样可以正常停止。
当然,如果有非常大量的线程来共享同一个资源使用和相同变量的操作 ,我觉得还是使用QRunnable ,因为底层会自动分配,而不需自己去写互斥锁和线程同步那些。
在前文中,有描述:使用QFuture来获取返回值,会阻塞主线程:
- // 将函数运行在子线程中
- QFuture<int> future = QtConcurrent::run(worker, &Worker::doSomething);
-
- // 通过result来获取结果,会直接阻塞主线程
- int res = future.result();
但是经过测试发现,如果套两层的话,就可以实现不会阻塞主线程,而同样获得结果:
- QtConcurrent::run([&](){
- // 将函数运行在子线程中
- QFuture<int> future = QtConcurrent::run(worker, &Worker::doSomething);
-
- // 通过result来获取结果,会直接阻塞主线程
- int res = future.result();
- qDebug()<<"res:"<<res;
- });
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。