当前位置:   article > 正文

QT中,QT线程四种写法的对比和写法示例_qt 线程池 invokemethod csdn

qt 线程池 invokemethod csdn

多线程小结

使用多线程,一般有两种方式,然后我又发现了几种,一共不下于四种:

  • 继承自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触发开始,直接调用还是主线程!!!


以下是方法的示例:

继承自QThread类的示例:

控制类:

  1. class MainWindow : public QMainWindow
  2. {
  3.    Q_OBJECT
  4. public:
  5.    MainWindow(QWidget *parent = nullptr);
  6.    ~MainWindow();
  7. private:
  8.    Ui::MainWindow *ui;
  9.    MyThread myThread;
  10. };
  11. MainWindow::MainWindow(QWidget *parent)
  12.   : QMainWindow(parent)
  13.   , ui(new Ui::MainWindow)
  14. {
  15.    ui->setupUi(this);
  16.    qDebug() << "MainUI()" << "thread:" << QThread::currentThreadId();
  17.    myThread.start();
  18.    connect(&myThread,&MyThread::workFinish,this,[=](){
  19.        qDebug()<<"OK";
  20.   });
  21. }
  22. MainWindow::~MainWindow()
  23. {
  24.    delete ui;
  25. }

子线程类:

  1. #include <QObject>
  2. #include <QThread>
  3. #include <QDebug>
  4. class MyThread : public QThread
  5. {
  6.    Q_OBJECT
  7. public:
  8.    explicit MyThread(QObject *parent = nullptr);
  9.    ~MyThread();
  10.    void run() override;
  11. signals:
  12.    void workFinish();
  13. private:
  14.    int num = 10;
  15. };
  16. // ***************************************************
  17. #include "myThread.h"
  18. MyThread::MyThread(QObject *parent) : QThread(parent)
  19. {
  20.    qDebug() << "MyThread()" << "thread:" << QThread::currentThreadId();
  21. }
  22. MyThread::~MyThread()
  23. {
  24.    qDebug() << "MyThread()~" << "thread:" << QThread::currentThreadId();
  25. }
  26. void MyThread::run()
  27. {
  28.    qDebug() << "run()" << "thread:" << QThread::currentThreadId();
  29.    while(num--){
  30.        qDebug()<<"run:"<<num;
  31.        QThread::sleep(1);
  32.   }
  33.    emit workFinish();
  34. }

打印结果:

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

继承自QObject类,使用moveToThread()的示例:

控制类:

  1. // 需要调用那个线程工作的控制类:
  2. class MainWindow : public QMainWindow
  3. {
  4.    Q_OBJECT
  5. public:
  6.    MainWindow(QWidget *parent = nullptr);
  7.    ~MainWindow();
  8. // 重点1:写触发信号:
  9. signals:
  10.    void doSomething(const QString &cmd);
  11.    void doThis();
  12. // 重点2:线程和工作对象,不要直接在构造函数创建
  13. private:
  14.    Ui::MainWindow *ui;
  15.    QThread  myWorkThread;
  16.    MyWorker myWorker;
  17. };
  18. MainWindow::MainWindow(QWidget *parent)
  19.   : QMainWindow(parent)
  20.   , ui(new Ui::MainWindow)
  21. {
  22.    ui->setupUi(this);
  23.    myWorker.moveToThread(&myWorkThread);
  24.    myWorkThread.start();
  25.    // 方式一:直接调用,还是主线程
  26.    // myWorker.doSomething("test");
  27.    // 方式二:信号关联
  28.    // 带参数:
  29.    connect(this,&MainWindow::doSomething,&myWorker,&MyWorker::doSomething);
  30.    emit doSomething("test");
  31.    // 不带参数:
  32.    connect(this,&MainWindow::doThis,&myWorker,&MyWorker::doThis);
  33.    emit doThis();
  34.    // 注意,这两个信号虽然几乎同时触发,但是因为在一个线程中,
  35.    // 所以有前后关系,必须doSomething完成,才可以去完成doThis
  36.    // 获取结束信号:
  37.    connect(&myWorker,&MyWorker::resultNotify,this,[=](){
  38.        qDebug()<<"doSomething finished!!!";
  39.   });
  40. }
  41. MainWindow::~MainWindow()
  42. {
  43.    myWorkThread.quit();
  44.    delete ui;
  45. }
 

工作类:

  1. #include <QObject>
  2. #include <QDebug>
  3. #include <QThread>
  4. class MyWorker : public QObject
  5. {
  6.    Q_OBJECT
  7. public:
  8.    explicit MyWorker(QObject *parent = nullptr);
  9.    ~MyWorker();
  10. private:
  11.    void func(const QString& cmd);
  12. public slots:
  13.    void doSomething(const QString& cmd);
  14.    void doThis();
  15. signals:
  16.    void resultNotify(const QString& des);  // 结束信号
  17. private:
  18.    bool flag = true;
  19.    int  num = 10;
  20. };
  21. // ***************************************
  22. MyWorker::MyWorker(QObject *parent) : QObject(parent)
  23. {
  24.    qDebug() << "Worker()" << "thread:" << QThread::currentThreadId();
  25. }
  26. MyWorker::~MyWorker()
  27. {
  28.    qDebug() << "~Worker()" << "thread:" << QThread::currentThreadId();
  29. }
  30. void MyWorker::func(const QString &cmd = "test")
  31. {
  32.    qDebug() << "func()" << "thread:" << QThread::currentThreadId();
  33.    while (num--) {
  34.        qDebug()<<"func:"<<cmd;
  35.        QThread::sleep(1);
  36.   }
  37.    emit resultNotify(cmd);
  38. }
  39. void MyWorker::doSomething(const QString &cmd)
  40. {
  41.    qDebug() << "doSomething()" << "thread:" << QThread::currentThreadId();
  42.    flag = true;
  43.    func(cmd);
  44. }
  45. void MyWorker::doThis()
  46. {
  47.    qDebug() << "doThis()" << "thread:" << QThread::currentThreadId();
  48.    int n=10;
  49.    while (n--) {
  50.        qDebug()<<"doThis:";
  51.        QThread::sleep(1);
  52.   }
  53. }

打印结果:

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

简单调用方法:QMetaObject::invokeMethod(myworker,"init");

  1. myThread = new QThread();
  2. myworker = new Worker();
  3. myThread->start();
  4. //myworker.init(); // 还是主线程,别用
  5. // 一般的调用方法:
  6. connect(this,&MainWindow::doInit,myworker,&Worker::init);
  7. emit doInit();
  8. // 简化写法:
  9. QMetaObject::invokeMethod(myworker,"init");

使用QtConcurrent开启线程:

有的版本可能需要在pro中,添加 QT += concurrent ,未测试。我发现我不加也能用。

调用方式:

1. QFuture<void> future = QtConcurrent::run(&worker, &Worker::doWork);

2. QtConcurrent::run([=](){    worker->UDPtest("直接调用即可"); });

3. run很多写法,用法不固定,我喜欢方法2,其他写法可自行百度。

工作类:比如有很多想单独开的线程函数

  1. #ifndef WORKER_H
  2. #define WORKER_H
  3. #include <QObject>
  4. #include <QThread>
  5. #include <QDebug>
  6. #include <QtCore>
  7. #include <QtConcurrent/QtConcurrent>
  8. class Worker : public QObject
  9. {
  10.    Q_OBJECT
  11. public:
  12.    explicit Worker(QObject *parent = nullptr);
  13.    Q_INVOKABLE void init();            // 类中自己调用
  14.    Q_INVOKABLE void doWork();          // 外部调用,无返回值
  15.    Q_INVOKABLE int  doSomething();     // 外部调用,有返回值
  16. signals:
  17.    void getResult(int value);
  18. };
  19. #endif // WORKER_H
  20. // ***********************************************************
  21. #include "worker.h"
  22. Worker::Worker(QObject *parent) : QObject(parent)
  23. {
  24.    // 自己内部调用
  25.    QFuture<void> future = QtConcurrent::run(this, &Worker::init);
  26. }
  27. // 内部调用
  28. void Worker::init()
  29. {
  30.    qDebug() << "init()" << "thread:" << QThread::currentThreadId();
  31.    int num = 8;
  32.    while (num--) {
  33.        qDebug()<<"init: "<<num;
  34.        QThread::sleep(1);
  35.   }
  36. }
  37. // 没有返回值的函数:
  38. void Worker::doWork()
  39. {
  40.    qDebug() << "doWork()" << "thread:" << QThread::currentThreadId();
  41.    int num = 10;
  42.    while (num--) {
  43.        qDebug()<<"doWork: "<<num;
  44.        QThread::sleep(1);
  45.   }
  46. }
  47. // 有返回值的函数:
  48. int  Worker::doSomething()
  49. {
  50.    qDebug() << "doSomething()" << "thread:" << QThread::currentThreadId();
  51.    int n = 5;
  52.    while (n--) {
  53.        qDebug()<<"doSomething: "<<n;
  54.        QThread::sleep(1);
  55.   }
  56.    emit getResult(0);
  57.    return 0;
  58. }

调用方式:

  1. #include <QMainWindow>
  2. #include <worker.h>
  3. #include <QThread>
  4. #include <QtCore>
  5. #include <QtConcurrent/QtConcurrent>
  6. class MainWindow : public QMainWindow
  7. {
  8.    Q_OBJECT
  9. public:
  10.    MainWindow(QWidget *parent = nullptr);
  11.    ~MainWindow(){};
  12.    void doWork();
  13.    void doSomething();
  14. private:
  15.    Ui::MainWindow *ui;
  16.    Worker worker;
  17. };
  18. #endif // MAINWINDOW_H
  19. // *************************************************************
  20. #include "mainwindow.h"
  21. MainWindow::MainWindow(QWidget *parent)
  22.   : QMainWindow(parent)
  23. {
  24.    ui->setupUi(this);
  25.    qDebug() << "QMainWindow()" << "thread:" << QThread::currentThreadId();
  26.    doWork();
  27.    doSomething();
  28. }
  29. // 调用无返回值的函数
  30. void MainWindow::doWork()
  31. {
  32.    // 将函数运行在子线程中
  33.    QFuture<void> future = QtConcurrent::run(&worker, &Worker::doWork);
  34. }
  35. // 调用有返回值的函数:
  36. void MainWindow::doSomething()
  37. {
  38.    // 将函数运行在子线程中
  39.    QFuture<int> future = QtConcurrent::run(&worker, &Worker::doSomething);
  40.    // 通过result来获取结果,会直接阻塞主线程
  41.    // int res = future.result();
  42.    // qDebug()<<"res:"<<res;
  43.    // 使用信号关联,就不会阻塞主线程
  44.    connect(&worker,&Worker::getResult,this,[=](int res){
  45.        qDebug()<<"res:"<<res;
  46.   });
  47. }

打印结果:

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

可以看到,一共四个线程。

Qtconcurrent::run()的简单用法

// 直接在其他线程中,调用即可。也可以传参。但是这个函数最好是解耦的,就是不要有太多关联:
// 但是如果内部有信号关联会有很多坑,需要加QEvenloop,和暂停信号。
QtConcurrent::run([=](){
    worker->UDPtest("UDP test");
});
工作类代码:
  1. class Worker : public QObject
  2. {
  3.    Q_OBJECT
  4. public:
  5.    //static Worker *instance;
  6.    //static Worker * getInstance();
  7.    
  8.    explicit Worker(QObject *parent = nullptr);
  9.    ~Worker();
  10.    void init();            // 类中自己调用
  11.    void doWork();          // 外部调用,无返回值
  12.    int  doSomething();     // 外部调用,有返回值
  13.    void test(const QString msg);
  14.    void UDPtest(const QString msg);
  15. signals:
  16.    void getResult(int value);
  17.    void setEnd();
  18. private slots:
  19.    void processPendingDatagrams_com1();
  20. private:
  21.    QUdpSocket *udpSocket_Com1;
  22.    bool thisFlag = true;
  23. };
  24. // ****************************
  25. #include "worker.h"
  26. //Worker* Worker::instance = nullptr;
  27. Worker::Worker(QObject *parent) : QObject(parent)
  28. {
  29.    // 自己内部调用
  30.    QFuture<void> future = QtConcurrent::run(this, &Worker::init);
  31. }
  32. Worker::~Worker()
  33. {
  34.    thisFlag = false;
  35.    emit setEnd();
  36. }
  37. //Worker *Worker::getInstance()
  38. //{
  39. //   if(instance == nullptr){
  40. //       instance = new Worker();
  41. //   }
  42. //   return instance;
  43. //}
  44. void Worker::init()
  45. {
  46.    qDebug() << "init()" << "thread:" << QThread::currentThreadId();
  47.    int num = 8;
  48.    while (num--) {
  49.        qDebug()<<"init: "<<num;
  50.        QThread::sleep(1);
  51.   }
  52. }
  53. void Worker::doWork()
  54. {
  55.    qDebug() << "doWork()" << "thread:" << QThread::currentThreadId();
  56.    int num = 10;
  57.    while (num--) {
  58.        qDebug()<<"doWork: "<<num;
  59.        QThread::sleep(1);
  60.   }
  61. }
  62. int  Worker::doSomething()
  63. {
  64.    qDebug() << "doSomething()" << "thread:" << QThread::currentThreadId();
  65.    int n = 5;
  66.    while (n--) {
  67.        qDebug()<<"doSomething: "<<n;
  68.        QThread::sleep(1);
  69.   }
  70.    emit getResult(0);
  71.    return 0;
  72. }
  73. void Worker::test(const QString msg)
  74. {
  75.    qDebug() << "test()" << "thread:" << QThread::currentThreadId();
  76.    int n = 3;
  77.    while (n--) {
  78.        qDebug()<<"test: "<<msg;
  79.        QThread::sleep(1);
  80.   }
  81. }
  82. void Worker::UDPtest(const QString msg)
  83. {
  84.    QEventLoop loop;
  85.    // 如果不加暂停信号,很多线程的loop会无法停止。
  86.    connect(this,&Worker::setEnd,[&](){
  87.        loop.quit();
  88.   });
  89.    qDebug() << "UDPtest()" << "thread:" << QThread::currentThreadId();
  90.    udpSocket_Com1 = new QUdpSocket();
  91.    udpSocket_Com1->bind(QHostAddress::AnyIPv4, 8002);
  92.    // 方法一:加QEventLoop
  93.    connect(udpSocket_Com1, &QIODevice::readyRead, new Worker,&Worker::processPendingDatagrams_com1);
  94.    // 方法二:加QEventLoop
  95.    // connect(udpSocket_Com1, &QIODevice::readyRead, [=](){
  96.    //     while (udpSocket_Com1->hasPendingDatagrams()) {
  97.    //         QByteArray datagram;
  98.    //         datagram.resize(udpSocket_Com1->pendingDatagramSize());
  99.    //         QHostAddress sender;
  100.    //         quint16 senderPort;
  101.    //         udpSocket_Com1->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
  102.    //         qDebug() << "Received udp datagram: " << QString(datagram);
  103.    //         udpSocket_Com1->writeDatagram(msg.toUtf8(), QHostAddress("127.0.0.1"), 8001);
  104.    //     }
  105.    // });
  106.    //if(!thisFlag) loop.quit();
  107.    loop.exec();
  108.    // 方法三:可以不加QEventLoop。但是要加循环,并且要用flag控制退出。
  109.    //   while (flag) {
  110.    //       while (udpSocket_Com1->hasPendingDatagrams()) {
  111.    //           QByteArray datagram;
  112.    //           datagram.resize(udpSocket_Com1->pendingDatagramSize());
  113.    //           QHostAddress sender;
  114.    //           quint16 senderPort;
  115.    //           udpSocket_Com1->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
  116.    //           qDebug() << "Received udp datagram: " << QString(datagram);
  117.    //           udpSocket_Com1->writeDatagram(msg.toUtf8(), QHostAddress("127.0.0.1"), 8001);
  118.    //       }
  119.    //   }
  120. }
  121. void Worker::processPendingDatagrams_com1()
  122. {
  123.    //qDebug("processPendingDatagrams_com1.....");
  124.    QUdpSocket* udpSocket = qobject_cast<QUdpSocket*>(sender());
  125.    if(!udpSocket) return;
  126.    //qDebug("udpSocket.....");
  127.    while (udpSocket->hasPendingDatagrams()) {
  128.        QByteArray datagram;
  129.        datagram.resize(udpSocket->pendingDatagramSize());
  130.        QHostAddress sender;
  131.        quint16 senderPort;
  132.        udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
  133.        qDebug() << "Received udp datagram: " << QString(datagram);
  134.   }
  135. }

使用QRunnable

QRunnable是一个轻量级的线程任务对象,适合处理一些相对简单的任务。可以将QRunnable放入QThreadPool中,由线程池自动管理线程,并发执行多个任务。QRunnable的优点是简单易用、轻量级,可以很好地利用现有的线程池资源,提高程序的并发性能。

要点:

  1. 如果需要信号传递,就需要继承自QObject

  2. 传参一般通过构造函数传参

  3. 如果是长时间任务,可以通过添加flag来控制停止

工作类:

  1. #ifndef WORKER_H
  2. #define WORKER_H
  3. #include <QObject>
  4. #include <QRunnable>
  5. #include <QDebug>
  6. #include <QThread>
  7. class Worker : public QObject,public QRunnable
  8. {
  9.    Q_OBJECT
  10. public:
  11.    explicit Worker(int num = 0, QObject *parent = nullptr);
  12.    void run() override;
  13.    void dowork_0();            // 简单任务,不传参    
  14.    void dowork_1();            // 简单任务,传参      
  15.    void dowork_2();            // 复杂长时间任务,无返回值
  16.    void dowork_3();            // 复杂长时间任务,有返回值
  17.    bool flag = true;
  18. signals:
  19.    void work_finished();
  20. private:
  21.    int n = 0;
  22. };
  23. #endif // WORKER_H
  24. // **************
  25. #include "worker.h"
  26. Worker::Worker(int num, QObject *parent) : QObject(parent)
  27. {
  28.    n = num;
  29. }
  30. void Worker::run()
  31. {
  32.    switch (n) {
  33.    case 0:
  34.        dowork_0();
  35.        break;
  36.    case 1:
  37.        dowork_1();
  38.        break;
  39.    case 2:
  40.        dowork_2();
  41.        break;
  42.    default:
  43.        dowork_3();
  44.        break;
  45.   }
  46. }
  47. void Worker::dowork_0()
  48. {
  49.    qDebug() << "dowork_0()" << "thread:" << QThread::currentThreadId();
  50.    int nn = 3;
  51.    while (nn--) {
  52.        n++;
  53.        qDebug()<<"dowork_0: "<<nn<<" n:"<<n;
  54.        QThread::sleep(1);
  55.   }
  56.    emit work_finished();
  57. }
  58. void Worker::dowork_1()
  59. {
  60.    qDebug() << "dowork_1()" << "thread:" << QThread::currentThreadId();
  61.    int nn = 5;
  62.    while (nn--) {
  63.        n++;
  64.        qDebug()<<"dowork_1: "<<nn<<" n:"<<n;
  65.        QThread::sleep(1);
  66.   }
  67.    emit work_finished();
  68. }
  69. void Worker::dowork_2()
  70. {
  71.    qDebug() << "dowork_2()" << "thread:" << QThread::currentThreadId();
  72.    int nn = 7;
  73.    while (flag && nn--) {
  74.        n++;
  75.        qDebug()<<"dowork_2: "<<nn<<" n:"<<n;
  76.        QThread::sleep(1);
  77.   }
  78.    emit work_finished();
  79. }
  80. void Worker::dowork_3()
  81. {
  82.    qDebug() << "dowork_3()" << "thread:" << QThread::currentThreadId();
  83.    int nn = 1;
  84.    while (flag && nn++) {
  85.        n++;
  86.        qDebug()<<"dowork_3: "<<nn<<" n:"<<n;
  87.        QThread::sleep(1);
  88.   }
  89.    emit work_finished();
  90. }

控制类:

    // 注意别写构造函数里面:
    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);
  1. // 关联:
  2. connect(work_0,&Worker::work_finished,this,[](){
  3. qDebug()<<"worker_0 finished";
  4. });
  5. connect(work_1,&Worker::work_finished,this,[](){
  6. qDebug()<<"worker_1 finished";
  7. });
  8. connect(work_2,&Worker::work_finished,this,[](){
  9. qDebug()<<"worker_2 finished";
  10. });
  11. connect(work_3,&Worker::work_finished,this,[](){
  12. qDebug()<<"worker_3 finished";
  13. });
  14. // 界面关闭可以控制底层线程关闭:
  15. MainWindow::~MainWindow()
  16. {
  17. work_0->flag = false;
  18. work_1->flag = false;
  19. work_2->flag = false;
  20. work_3->flag = false;
  21. delete ui;
  22. }

线程的销毁

这里要注意!!!!!!!

这里要注意!!!!!!!

这里要注意!!!!!!!

使用QRunnable和QtConcurrent都是自动创建和销毁的,实在不行加个flag,不用担心资源浪费。

但是Qthread和movetoThread就需要手动管理停止。

QThread的手动管理线程的创建和销毁:

  1. // 创建线程
  2. QThread* thread = new QThread;
  3. MyWorker* worker = new MyWorker; // 自定义的工作对象
  4. worker->moveToThread(thread); // 将工作对象移动到新创建的线程中
  5. // 连接信号和槽
  6. connect(thread, &QThread::started, worker, &MyWorker::doWork);
  7. connect(worker, &MyWorker::workFinished, thread, &QThread::quit);
  8. connect(thread, &QThread::finished, worker, &MyWorker::deleteLater);
  9. connect(thread, &QThread::finished, thread, &QThread::deleteLater);
  10. // 启动线程
  11. thread->start();
  12. // 销毁线程
  13. // 在合适的时机,如应用程序退出前
  14. thread->quit();
  15. thread->wait(); // 等待线程结束
  16. delete thread;

moveToThread()的手动管理线程的创建和销毁:

  1. // 创建线程
  2. QThread* thread = new QThread;
  3. MyObject* object = new MyObject; // 自定义的QObject对象
  4. object->moveToThread(thread); // 将对象移动到新创建的线程中
  5. // 连接信号和槽
  6. connect(thread, &QThread::started, object, &MyObject::onThreadStarted);
  7. connect(object, &MyObject::finished, thread, &QThread::quit);
  8. connect(thread, &QThread::finished, object, &MyObject::deleteLater);
  9. connect(thread, &QThread::finished, thread, &QThread::deleteLater);
  10. // 启动线程
  11. thread->start();
  12. // 销毁线程
  13. // 在合适的时机,如应用程序退出前
  14. thread->quit();
  15. thread->wait(); // 等待线程结束
  16. delete thread;

小结

        由打印结果可知,很多时候我们以为是子线程,其实并不是子线程,而且该关联的信号必须关联,重写run方法,那么只有run里面的才是子线程。

        特别是很多人推荐使用的moveToThread,其实坑一样很多!!!如果是继承自QObject,即便moveToThread()以后,直接使用函数也还是主线程,必须使用信号关联触发才是子线程。关键是,如果你只创建了一个Qthread,这还不是异步操作的,只是单独开了一个子线程而已,并不是几个emit以后,立马创建了多个并行的子线程,并不是啊!你得多开几个QThread才行!

        最开始只是想排查Bug, 但是经过我的测试,竟然发现还有 Qtconcurrent::run()的简单用法,只要注意其中的一些注意事项,就可以做到简单易用。然后再发现还有QRunnable的方法,于是写出来了这篇文章。其中Qtconcurrent::run()在解耦好的单独运行函数的时候超级好用!

        但是在测试的过程中,发现了很多坑,例如资源共享,同步的问题,还有线程停止的问题!!!任何一种线程方法,如果不添加flag的话,很容易在后台运行而无法停止,因为在QT中,GUI线程和底层是分开的,如果界面点击了关闭,底层不控制关闭自然是停止不了的,容易出问题,所以最好长时间的任务都做信号或者一个flag来控制关闭。


最终总结:

最好用的是moveToThread 和 Qtconcurrent::run()

moveToThread 的最佳写法:

  1. //在控制的类头文件中写入
  2. QThread *myThread;
  3. Worker *myworker;
  4. //在构造函数中:
  5. myThread = new QThread();
  6. myworker = new Worker();
  7. myThread->start();
  8. myworker->moveToThread(myThread);
  9. ***************调用*****************
  10. // 主函数调用
  11. // myworker->init();
  12. // 信号触发
  13. //connect(this,&MainWindow::doInit,myworker,&Worker::init);
  14. //emit doInit();
  15. // 调用方式(信号触发):直接在子线程运行,而且不影响myworker.init()的使用
  16. QMetaObject::invokeMethod(myworker,"test"); // 子线程
  17. myworker->init(); // 还是主线程
  18. // 需要传参的时候
  19. // QMetaObject::invokeMethod(myworker, "mySlot", Qt::DirectConnection, Q_ARG(int, 42), Q_ARG(float, 100));
  20. *********************************工作类写法******************************************
  21. // 以上这种方式,只要Worker类中,在publit slots:下面有一个test函数就可以。
  22. class Worker : public QObject
  23. {
  24. Q_OBJECT
  25. public:
  26. explicit Worker(QObject *parent = nullptr);
  27. public slots:
  28. int init();
  29. void test();
  30. void mySlot(int ,float);
  31. signals:
  32. private:
  33. int nn = 0;
  34. };
  35. **************************************
  36. 【注意】:
  37. 使用此方法,看似是在一个类中,资源可以共享,实际上数据确实可以共享,比如那个nn,可是内部指针数据有些是无法正常跨线程使用的,比如UDPsocket和串口的,一般情况是都不能跨线程使用,会出问题。但是经过我的测试,加一个延时就解决了。
  38. // 信号触发的简单写法:
  39. QMetaObject::invokeMethod(myworker,"init"); // 内部写了UDPsocket的创建,bind和connect
  40. QThread::sleep(1);
  41. myworker->test(); // 内部写了UDPsocket的定时发送

Qtconcurrent::run()最佳写法:

  1. Qtconcurrent::run([&](){
  2. myworker.init();
  3. });
  4. // 此方法注意内部信号关联的问题。connect需要加eventloop

补充:

意外发现1:多线程资源共享

        后来根据我的测试发现,跨线程使用的时候,有时候好像是不能跨线程共享资源,有时候又可以,于是我写了下面的代码,经过测试,在init下面加一个延时,就可以有效的实现跨线程共享资源,即便是UDPsocket一样可以实现:

工作类测试demo:

  1. // 工作类头文件:
  2. #ifndef WORKER_H
  3. #define WORKER_H
  4. #include <QObject>
  5. #include <QThread>
  6. #include <QDebug>
  7. #include <QUdpSocket>
  8. class Worker : public QObject
  9. {
  10. Q_OBJECT
  11. public:
  12. explicit Worker(QObject *parent = nullptr);
  13. public slots:
  14. int init();
  15. void test();
  16. signals:
  17. private:
  18. int nn = 0;
  19. QUdpSocket *udpSocket;
  20. };
  21. #endif // WORKER_H
  22. // 工作类cpp文件
  23. #include "worker.h"
  24. Worker::Worker(QObject *parent) : QObject(parent)
  25. {
  26. qDebug() << "Worker()" << "thread:" << QThread::currentThreadId();
  27. }
  28. int Worker::init()
  29. {
  30. qDebug() << "init()" << "thread:" << QThread::currentThreadId();
  31. udpSocket = new QUdpSocket();
  32. udpSocket->bind(QHostAddress::AnyIPv4, 8002);
  33. connect(udpSocket, &QIODevice::readyRead, [&](){
  34. // 因为是引用,仍然是创建socket的那个线程。
  35. qDebug() << "//" << "thread:" << QThread::currentThreadId();
  36. qDebug() << "UDPtest2 udpSocket_Com1 addr: "<< udpSocket;
  37. while (udpSocket->hasPendingDatagrams()) {
  38. QByteArray datagram;
  39. datagram.resize(udpSocket->pendingDatagramSize());
  40. QHostAddress sender;
  41. quint16 senderPort;
  42. udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
  43. qDebug() << "Received udp datagram: " << QString(datagram);
  44. udpSocket->writeDatagram(QString("haha").toUtf8(), QHostAddress("127.0.0.1"), 8001);
  45. }
  46. });
  47. return 0;
  48. }
  49. void Worker::test()
  50. {
  51. qDebug() << "test()" << "thread:" << QThread::currentThreadId();
  52. int num = 10;
  53. while (num--) {
  54. nn++;
  55. qDebug()<<"test nn:"<<nn;
  56. udpSocket->writeDatagram(QString("nn:%1 ").arg(nn).toUtf8(), QHostAddress("127.0.0.1"), 8001);
  57. QThread::sleep(1);
  58. }
  59. }

工作类使用Demo:

  1. // 类中:
  2. private
  3. QThread *myThread;
  4. Worker *myworker;
  5. // 调用:
  6. myThread = new QThread();
  7. myworker = new Worker();
  8. myThread->start();
  9. myworker->moveToThread(myThread);
  10. // 信号触发的简单写法:开启子线程
  11. QMetaObject::invokeMethod(myworker,"init");
  12. // 重点:这里加延时就可以正常运行!!!不加就报错
  13. QThread::sleep(1);
  14. // 还是主线程运行:
  15. 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 ,因为底层会自动分配,而不需自己去写互斥锁和线程同步那些。

意外发现2:QtConcurrent::run里面再套QtConcurrent::run

        在前文中,有描述:使用QFuture来获取返回值,会阻塞主线程:

  1. // 将函数运行在子线程中
  2. QFuture<int> future = QtConcurrent::run(worker, &Worker::doSomething);
  3. // 通过result来获取结果,会直接阻塞主线程
  4. int res = future.result();

        但是经过测试发现,如果套两层的话,就可以实现不会阻塞主线程,而同样获得结果:

  1. QtConcurrent::run([&](){
  2. // 将函数运行在子线程中
  3. QFuture<int> future = QtConcurrent::run(worker, &Worker::doSomething);
  4. // 通过result来获取结果,会直接阻塞主线程
  5. int res = future.result();
  6. qDebug()<<"res:"<<res;
  7. });

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/104559
推荐阅读
相关标签
  

闽ICP备14008679号