• Qt多线程实现方式-moveToThread及其注意事项



    特别注意:
    通过obj->movetothread(thread)并不是将Object中所有的函数都移动到子线程当中。只有通过槽函数连接的才在子线程中,可以通过qDebug()打印其currentThreadId多试试。

    Chapter1 Qt多线程实现方式-moveToThread

    原文链接:https://blog.csdn.net/k331922164/article/details/70990239

    一、Qt下使用线程主要有两种方法。

    一种是传统的继承QThread类,重写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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    该方法已经落伍了,主要原因线程不安全,需要自己手动加锁,比较麻烦,所以推荐使用方法二。

    定义一个工作线程(Worker类)继承QObject,在主线程(Controller类)中创建QThread对象、Worker对象,Worker对象调用moveToThread方法。

    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 &);
     };
    
    • 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
    • 35
    • 36
    • 37

    这样一来,整个Worker对象都移入线程中(线程安全),然后在主线程中每发射一次信号给工作线程,工作线程的槽函数就执行一次。

    工作线程执行完,再发射信号到主线程中,以便释放内存。

    新建一个Woker对象和一个QThread对象,才能创建一个线程,如果要创建若干多个线程,则需要若干个Woker对象和QThread对象了。

    二、Qt下创建多线程也有两种方法。

    一种是使用容器(如:QVector类、QList类)去装入多个Worker对象和多个QThread对象,使用[](类似数组的操作),即可访问单个对象。

    另一种是使用并发类QtConcurrent。

    三、其它问题。

    1、使用线程时,能编译通过但是提示段错误,原因是没有在构造函数内new Worker对象和QThread对象。

    2、内存泄漏,线程做完时,需要调用quit方法、wait方法,还要delete Worker对象和QThread对象。如果后面还要使用该线程,则再加上new Worker对象和QThread对象。
    3、调用任务管理器,可以观察到是否出现内存泄漏。没有任何操作,内存使用量不停增加,即为内存泄漏。

    Chapter2 QT多线程接收串口数据

    原文链接:https://blog.csdn.net/aptblaze/article/details/118003195

    1.前言

    QT多线程的使用,和绝大数人一样,犯了错误(请查阅Qt开发人员( Bradley T. Hughes)Blog中的文章 you are-doing-it-wrong介绍)。为了解决问题,网上查阅学习了几十篇文章,基本都是错误的使用方法,或者不完整,未能给予正确的引导。
    为方便后来学习者,少走弯路,于是自己动手写了一下程序,过程不再赘述,只以完整的案例进行教学,内部注释较多,可供大家阅读、思考。

    2.功能作用

    使用多线程,避免上位机软件与单片机等硬件设备高速通讯时,造成软件界面假死、丢包等现象。同时对串口进行了简单的封装,方便调用。本文提供了完整的源代码,方便测试。有较详细的注释方便阅读、思考。编译环境为QT5.8.0,Qt Creator4.2.1

    3.软件测试效果

    在这里插入图片描述
    在这里插入图片描述

    4.基本步骤

    (1)pro文件添加QT5自带的头文件

    QT       += serialport
    
    • 1

    (2)serialworker.h头文件

    #include "serialworker.h"
    
    SerialWorker::SerialWorker(QSerialPort *ser, QObject *parent) : QObject(parent),serial(ser)
    {
    
    }
    QString SerialWorker::ByteArrayToHexString(QByteArray data)
    {
        QString ret(data.toHex().toUpper());
        int len = ret.length()/2;
        qDebug()<<"收到字节长度为:"<<len;
        for(int i=1;i<len;i++)
        {
            ret.insert(2*i+i-1," ");
        }
        return ret;
    }
    
    void SerialWorker::doDataReciveWork()
    {    
        QByteArray buffer = serial->readAll();
        // 2.进行数据处理
        QString result = ByteArrayToHexString(buffer);
        qDebug() <<  "子线程收到数据:" << result << "线程ID:" << QThread::currentThreadId();
        // 3.将结果发送到主线程
        emit sendResultToGui(result);
    }
    
    
    • 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

    (3)mainwindow.h文件

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        setWindowTitle("子线程读串口");
        this->setMinimumSize(600,248);
        this->setMaximumSize(1200,496);
    
        InitSerialPortName();
    
        //1.新建串口处理子线程
        SerialWorker *ser = new SerialWorker(&serial_1);
        ser->moveToThread(&serialThread_1);
    
       // 2.连接信号和槽
        QString s;
        connect(&serialThread_1, &QThread::finished, ser, &QObject::deleteLater);           // 线程结束,自动删除对象
        connect(&serial_1, &QSerialPort::readyRead, ser, &SerialWorker::doDataReciveWork); // 主线程通知子线程接收数据的信号
        connect(ser, &SerialWorker::sendResultToGui, this, &MainWindow::handleResults);    // 主线程收到数据结果的信号
      //  connect(ser,SIGNAL(sendResultToGui(QString)), this, SLOT(handleResults(QString)));     //主线程收到数据结果的信号写法2
    
        // 3.开始运行子线程
        serialThread_1.start();                   // 线程开始运行
    }
    
    MainWindow::~MainWindow()
    {
        serialThread_1.quit();
        serialThread_1.wait();
        delete ui;
    }
    
    void MainWindow::InitSerialPortName()
    {
        // 清空下拉框
        ui->box_portName->clear();
    
        //通过QSerialPortInfo查找可用串口
        foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
        {
            QString showName = info.portName();
            qDebug() << showName+info.description();
            ui->box_portName->addItem(showName);
        }
        //波特率
            QStringList baudrateList;
            baudrateList<<"4800"<<"9600"<<"19200"<<"38400"<<"57600"<<"115200";
            ui->box_baudrate->addItems(baudrateList);//添加下拉列表选项
            ui->box_baudrate->setCurrentText("115200");//界面中初始值
        //    ui->box_baudrate->setView(new QListView(this));//该设置是配合qss的,不然item行高设置没效果
        //数据位
            QStringList databitList;
            databitList<<"5"<<"6"<<"7"<<"8";
            ui->box_dataBits->addItems(databitList);
            ui->box_dataBits->setCurrentText("8");
    //      ui->box_dataBits->setView(new QListView(this));
            //校验位
            QStringList parityList;
            parityList<<"无"<<"奇"<<"偶";
            ui->box_parityBit->addItems(parityList);
            ui->box_parityBit->setCurrentText("No");
    //      ui->box_parityBit->setView(new QListView(this));
            //停止位
            QStringList stopbitList;
            stopbitList<<"1"<<"2";
            ui->box_stopBit->addItems(stopbitList);
            ui->box_stopBit->setCurrentText("1");
    //      ui->box_stopBit->setView(new QListView(this));
            //流控制
    //        QStringList flowctrlList;
    //        flowctrlList<<"No"<<"Hardware"<<"Software";
    //        ui->boxFlowControl->addItems(flowctrlList);
    //        ui->boxFlowControl->setCurrentText("No");
          ui->boxFlowControl->setView(new QListView(this));
    }
    
    void MainWindow::on_btn_openPort_clicked()
    {
        if(ui->btn_openPort->text()==QString("打开串口"))
        {
            //设置串口名
            QString portName = (ui->box_portName->currentText()).split(":").at(0);
            qDebug() <<"当前打开串口为:"<<portName;
            serial_1.setPortName(portName);
    
            //设置波特率
            serial_1.setBaudRate(ui->box_baudrate->currentText().toInt());
    
            //设置停止位
            if(ui->box_stopBit->currentText() == "1")
                serial_1.setStopBits(QSerialPort::OneStop);
            else if(ui->box_stopBit->currentText() == "2")
                serial_1.setStopBits(QSerialPort::TwoStop);
    
            //设置数据位数
            if(ui->box_dataBits->currentText() == "8")
                serial_1.setDataBits(QSerialPort::Data8);
            else if(ui->box_dataBits->currentText() == "7")
                serial_1.setDataBits(QSerialPort::Data7);
            else if(ui->box_dataBits->currentText() == "6")
                serial_1.setDataBits(QSerialPort::Data6);
            else if(ui->box_dataBits->currentText() == "5")
                serial_1.setDataBits(QSerialPort::Data5);
    
            //设置奇偶校验
            if(ui->box_parityBit->currentText() == "无")
                serial_1.setParity(QSerialPort::NoParity);
            else if(ui->box_parityBit->currentText() == "偶")
                serial_1.setParity(QSerialPort::EvenParity);
            else if(ui->box_parityBit->currentText() == "奇")
                serial_1.setParity(QSerialPort::OddParity);
    
    //        //设置流控制
    //        serial_1.setFlowControl(QSerialPort::NoFlowControl);
    
            //打开串口
            if(!serial_1.open(QIODevice::ReadWrite))
            {
                QMessageBox::about(NULL, "提示", "无法打开串口!");
                return;
            }
    
            //下拉菜单控件失能
            ui->box_portName->setEnabled(false);
            ui->box_baudrate->setEnabled(false);
            ui->box_dataBits->setEnabled(false);
            ui->box_parityBit->setEnabled(false);
            ui->box_stopBit->setEnabled(false);
    
            ui->btn_openPort->setText(QString("关闭串口"));
    
        }
        else
        {
            //关闭串口
            serial_1.close();
            //下拉菜单控件使能
            ui->box_portName->setEnabled(true);
            ui->box_baudrate->setEnabled(true);
            ui->box_dataBits->setEnabled(true);
            ui->box_parityBit->setEnabled(true);
            ui->box_stopBit->setEnabled(true);
            ui->btn_openPort->setText(QString("打开串口"));
        }
    }
    
    void MainWindow::on_btn_clearText_clicked()
    {
        ui->browser_dataReceive->clear();
    }
    
    void MainWindow::handleResults(const QString &result)
    {
        qDebug() <<  "主线程收到结果数据:" << result << "线程ID:" << QThread::currentThreadId();
        //从界面中读取以前收到的数据
        QString oldString = ui->browser_dataReceive->toPlainText()+'\n';
        oldString = oldString + QString(result);
        //清空以前的显示
        ui->browser_dataReceive->clear();
        //重新显示
        ui->browser_dataReceive->append(oldString);
    }
    
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167

    (4)mainwindow.cpp文件

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        setWindowTitle("子线程读串口");
        this->setMinimumSize(600,248);
        this->setMaximumSize(1200,496);
    
        InitSerialPortName();
    
        //1.新建串口处理子线程
        SerialWorker *ser = new SerialWorker(&serial_1);
        ser->moveToThread(&serialThread_1);
    
       // 2.连接信号和槽
        QString s;
        connect(&serialThread_1, &QThread::finished, ser, &QObject::deleteLater);           // 线程结束,自动删除对象
        connect(&serial_1, &QSerialPort::readyRead, ser, &SerialWorker::doDataReciveWork); // 主线程通知子线程接收数据的信号
        connect(ser, &SerialWorker::sendResultToGui, this, &MainWindow::handleResults);    // 主线程收到数据结果的信号
      //  connect(ser,SIGNAL(sendResultToGui(QString)), this, SLOT(handleResults(QString)));     //主线程收到数据结果的信号写法2
    
        // 3.开始运行子线程
        serialThread_1.start();                   // 线程开始运行
    }
    
    MainWindow::~MainWindow()
    {
        serialThread_1.quit();
        serialThread_1.wait();
        delete ui;
    }
    
    void MainWindow::InitSerialPortName()
    {
        // 清空下拉框
        ui->box_portName->clear();
    
        //通过QSerialPortInfo查找可用串口
        foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
        {
            QString showName = info.portName();
            qDebug() << showName+info.description();
            ui->box_portName->addItem(showName);
        }
        //波特率
            QStringList baudrateList;
            baudrateList<<"4800"<<"9600"<<"19200"<<"38400"<<"57600"<<"115200";
            ui->box_baudrate->addItems(baudrateList);//添加下拉列表选项
            ui->box_baudrate->setCurrentText("115200");//界面中初始值
        //    ui->box_baudrate->setView(new QListView(this));//该设置是配合qss的,不然item行高设置没效果
        //数据位
            QStringList databitList;
            databitList<<"5"<<"6"<<"7"<<"8";
            ui->box_dataBits->addItems(databitList);
            ui->box_dataBits->setCurrentText("8");
    //      ui->box_dataBits->setView(new QListView(this));
            //校验位
            QStringList parityList;
            parityList<<"无"<<"奇"<<"偶";
            ui->box_parityBit->addItems(parityList);
            ui->box_parityBit->setCurrentText("No");
    //      ui->box_parityBit->setView(new QListView(this));
            //停止位
            QStringList stopbitList;
            stopbitList<<"1"<<"2";
            ui->box_stopBit->addItems(stopbitList);
            ui->box_stopBit->setCurrentText("1");
    //      ui->box_stopBit->setView(new QListView(this));
            //流控制
    //        QStringList flowctrlList;
    //        flowctrlList<<"No"<<"Hardware"<<"Software";
    //        ui->boxFlowControl->addItems(flowctrlList);
    //        ui->boxFlowControl->setCurrentText("No");
          ui->boxFlowControl->setView(new QListView(this));
    }
    
    void MainWindow::on_btn_openPort_clicked()
    {
        if(ui->btn_openPort->text()==QString("打开串口"))
        {
            //设置串口名
            QString portName = (ui->box_portName->currentText()).split(":").at(0);
            qDebug() <<"当前打开串口为:"<<portName;
            serial_1.setPortName(portName);
    
            //设置波特率
            serial_1.setBaudRate(ui->box_baudrate->currentText().toInt());
    
            //设置停止位
            if(ui->box_stopBit->currentText() == "1")
                serial_1.setStopBits(QSerialPort::OneStop);
            else if(ui->box_stopBit->currentText() == "2")
                serial_1.setStopBits(QSerialPort::TwoStop);
    
            //设置数据位数
            if(ui->box_dataBits->currentText() == "8")
                serial_1.setDataBits(QSerialPort::Data8);
            else if(ui->box_dataBits->currentText() == "7")
                serial_1.setDataBits(QSerialPort::Data7);
            else if(ui->box_dataBits->currentText() == "6")
                serial_1.setDataBits(QSerialPort::Data6);
            else if(ui->box_dataBits->currentText() == "5")
                serial_1.setDataBits(QSerialPort::Data5);
    
            //设置奇偶校验
            if(ui->box_parityBit->currentText() == "无")
                serial_1.setParity(QSerialPort::NoParity);
            else if(ui->box_parityBit->currentText() == "偶")
                serial_1.setParity(QSerialPort::EvenParity);
            else if(ui->box_parityBit->currentText() == "奇")
                serial_1.setParity(QSerialPort::OddParity);
    
    //        //设置流控制
    //        serial_1.setFlowControl(QSerialPort::NoFlowControl);
    
            //打开串口
            if(!serial_1.open(QIODevice::ReadWrite))
            {
                QMessageBox::about(NULL, "提示", "无法打开串口!");
                return;
            }
    
            //下拉菜单控件失能
            ui->box_portName->setEnabled(false);
            ui->box_baudrate->setEnabled(false);
            ui->box_dataBits->setEnabled(false);
            ui->box_parityBit->setEnabled(false);
            ui->box_stopBit->setEnabled(false);
    
            ui->btn_openPort->setText(QString("关闭串口"));
    
        }
        else
        {
            //关闭串口
            serial_1.close();
            //下拉菜单控件使能
            ui->box_portName->setEnabled(true);
            ui->box_baudrate->setEnabled(true);
            ui->box_dataBits->setEnabled(true);
            ui->box_parityBit->setEnabled(true);
            ui->box_stopBit->setEnabled(true);
            ui->btn_openPort->setText(QString("打开串口"));
        }
    }
    
    void MainWindow::on_btn_clearText_clicked()
    {
        ui->browser_dataReceive->clear();
    }
    
    void MainWindow::handleResults(const QString &result)
    {
        qDebug() <<  "主线程收到结果数据:" << result << "线程ID:" << QThread::currentThreadId();
        //从界面中读取以前收到的数据
        QString oldString = ui->browser_dataReceive->toPlainText()+'\n';
        oldString = oldString + QString(result);
        //清空以前的显示
        ui->browser_dataReceive->clear();
        //重新显示
        ui->browser_dataReceive->append(oldString);
    }
    
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167

    (5)serialworker.cpp文件

    #include "serialworker.h"
    
    SerialWorker::SerialWorker(QSerialPort *ser, QObject *parent) : QObject(parent),serial(ser)
    {
    
    }
    QString SerialWorker::ByteArrayToHexString(QByteArray data)
    {
        QString ret(data.toHex().toUpper());
        int len = ret.length()/2;
        qDebug()<<"收到字节长度为:"<<len;
        for(int i=1;i<len;i++)
        {
            ret.insert(2*i+i-1," ");
        }
        return ret;
    }
    
    void SerialWorker::doDataReciveWork()
    {    
        QByteArray buffer = serial->readAll();
        // 2.进行数据处理
        QString result = ByteArrayToHexString(buffer);
        qDebug() <<  "子线程收到数据:" << result << "线程ID:" << QThread::currentThreadId();
        // 3.将结果发送到主线程
        emit sendResultToGui(result);
    }
    
    
    • 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

    Chapter3 利用Qt多线程机制实现双路串口数据流的接收和发送

    原文链接:https://blog.csdn.net/SmartTiger_CSL/article/details/104383717

    在上一篇文章的基础上,编写了一个对话框程序,可同时收发两路串口数据,每一路串口均在独立的子线程中实现。增加了清空edit的按钮。

    1. 主程序界面

    在这里插入图片描述

    2. 两个子线程的线程号(调试信息中输出)

    在这里插入图片描述

    主线程的ID号为0x179c,两个串口子线程类的构造均是在主线程中,串口的启动、接收数据均在各自的子线程中,子线程ID号分别在0x14e4和0x5b0。而串口的关闭是在主线程中。这是和connect的配置有关。代码如下:

    #include "serialcontroller.h"
    #include 
    
    SerialController::SerialController(QObject *parent) : QObject(parent)
    {
        m_portId = -1;
    }
    
    SerialController::~SerialController()
    {
        if(m_serialThread.isRunning())
        {
            m_serialPort->closePort();
            delete m_serialPort;
            m_serialThread.quit();
            m_serialThread.wait();
         }
    
    }
    void SerialController::initCtrl(int portId,QString portName,long portBaud)
    {
        m_portId = portId;
        m_portName = portName;
        m_portBaud = portBaud;
        qDebug()<<"Controller is running in main thread: "<<QThread::currentThreadId();
    
        //实例对象保存在堆上,没有父对象的指针要想正常销毁,需要将线程的 finished() 信号关联到 QObject 的 deleteLater() 让其在正确的时机被销毁
    
        m_serialPort = new SerialPort(m_portId,m_portName,m_portBaud);
        //m_serialPort对象不能有父对象。
        m_serialPort->moveToThread(&m_serialThread);
    
        connect(this,&SerialController::startRunning,m_serialPort,&SerialPort::startPort);
        connect(&m_serialThread,&QThread::finished,m_serialPort,&QObject::deleteLater);
        connect(this,&SerialController::ctrlSendData,m_serialPort,&SerialPort::write_Data);//从主线程发来的数据写入串口
        connect(m_serialPort,&SerialPort::receive_data,this,&SerialController::ctrlReceiveData);//从串口读取的数据发送给主线程
    }
    void SerialController::startCtrl()
    {
        m_serialThread.start();
        emit startRunning();
    
    }
    void SerialController::stopCtrl()
    {
        if(m_serialThread.isRunning())
        {       
            m_serialPort->closePort();
            m_serialThread.quit();//会自动发送finished信号
            m_serialThread.wait();       
        }
    }
    
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    串口的启动和接收数据所在的函数startPort与QThread类的connect信号关联,因此是在子线程中执行;而串口的关闭函数closePort没有与connect关联,不是槽函数,是在SerialController类的stopCtrl中执行,SerialController存在于主线中,因此closePort在主线程中执行。
    因此,这里验证里上一篇文章中的重点(3):
    (3)controller、worker 对象到底在哪个线程?「在哪创建就属于哪」这句话放在任何地方都是适用的。而 moveToThread() 函数的作用是将槽函数在指定的线程中被调用。也就是说controller、worker对象均在主线程中。除了绑定在connect上的槽函数(及槽函数体调用的函数)外,worker的其余函数也在主线程中执行。这个connect发送者可以是SerialController本身(this),也可以是m_serialThread
    moveToThread()并不是将整个worker 对象“搬移”到controller线程中,而是将connect中的槽函数放到controller线程中执行。不注意这一点的话,很可能出现“QObject::Cannot create children for a parent that is in a different thread”问题。或者出现“耗时工作代码”仍在主线程中运行的情况。

    在这里插入图片描述

    Chapter4 QT多线程——解决因串口数据过多而导致程序界面崩溃的问题

    原文链接:https://blog.csdn.net/DIVIDADA/article/details/131209469

    一 前言

    ​最近做了一个QT上位机。上位机通过串口接收下位机的数据,下位机对数据进行计算处理并将处理后的结果显示在程序界面上。程序中,下位机每隔200ms给QT上位机发送一帧20字节的数据,但是会经常出现界面卡顿并崩溃的情况,当然,我可以通过修改下位机数据发送频率(如1S发送一帧数据)的方法来解决这个问题,但是这种解决方法不太优雅且有一定的缺陷,于是我使用了QT多线程——除了GUI线程外,另开一个子线程,专门用来处理串口数据。

    在这里插入图片描述

    二 步骤

    2.1 方法描述
    ​QThread 类提供了一个与平台无关的管理线程的方法。一个 QThread 对象管理一个线程。QThread 的执行从 run() 函数的执行开始,在 Qt 自带的 QThread 类中,run() 函数通过调用 exec() 函数来启动事件循环机制,并且在线程内部处理 Qt 的事件。在 Qt 中建立线程的主要目的就是为了用线程来处理那些耗时的后台操作,从而让主界面能及时响应用户的请求操作。

    定义一个继承于 QObject 的 worker 类,在 worker 类中定义一个槽函数 doWork(),这个函数中接收和处理串口数据,并将处理好的数据发送给GUI线程。
    在GUI线程的 controller 类中,新建一个 QThread 的对象和 woker 类对象,使用 moveToThread() 方法将 worker 对象的事件循环全部交由 QThread 对象处理。
    建立相关的信号函数和槽函数进行连接,GUI线程发出信号触发 QThread 的槽函数处理串口数据,数据处理完毕后QThread 子线程发送信号触发GUI线程槽函数接收处理好的串口数据。

    2.2 代码实现

    2.2.1 子线程中
    在这里插入图片描述
    新建一个继承与QObject的子线程类:SerialWorker
    在这里插入图片描述
    上面为修改serialworker.h
    在这里插入图片描述
    上面为修改serialworker.cpp

    2.2.2 GUI线程中
    在这里插入图片描述
    上面为修改widget.h
    在这里插入图片描述
    上面为修改widget.c

    三 总结

    经测试,使用子线程来收发串口数据,可以提升程序运行的稳定性,解决因为串口数据量过大而造成界面卡顿的问题。

    Chapter5 Qt子线程、多线程使用串口 QSerialPort QThread QObject

    https://blog.csdn.net/u014779536/article/details/111721947
    在这里插入图片描述

    Chapter6 Qt实战—多线程的串口接收类QSerialPort (问题分析的很到位)

    原文链接:https://blog.csdn.net/qq_39262215/article/details/128526748

    一、典型的问题:

    在我们的多线程编程中,一般主线程负责界面的刷新显示,而子线程负责一些耗时的操作,例如当我们使用QSerialPort的时候,我们希望QSerialPort的读写操作在子线程中进行。

    QObject: Cannot create children for a parent that is in a different thread.

    那么如果在主线程中创建QSerialPort对象,通过指针的形式将QSerialPort传入到了子线程中,在子线程的函数内使用QSerialPort指针操作读写函数,就会出现上面的错误提示!

    原因:在主线程中创建了QSerialPort对象在子线程中调用,或者在子线程中创建然后在主线程中调用了。这就是跨线程调用引起的问题!

    对于继承QThread重写run函数的情况,往往容易在run外部定义QSerialport *port = new QSerialport()对象,然后在run中调用port->readAll()等函数,然而根据QThread的特性,只有run函数才运行在新的子线程中,所以这里就跨线程调用了 QSerialport对象,会出现上述报错。

    **注意:**其实不止是QSerialPort,只要是QIODevice类型的对象都无法进行跨线程调用,这里是由于Qt的信号和槽机制的问题,再有就是QSerialPort是异步的,什么意思呢?就是说使用QSerialPort->readData()函数的时候不会阻塞,会立刻返回,其实QSerialPort->readData()只是在读取一个缓冲区而已,而在另一个线程中,有数据的话就会将数据写入到这个缓冲区。

    解决办法:

    派生类外部定义QSerialPort指针,在run函数中再定义对象,并且数据的读、写都在run函数中进行。

    上述方法可以解决报错,但是针对不仅仅具有读串口数据需求、还需要向串口写数据的情况,显然这样的方法会让run函数变得十分臃肿,代码的维护十分麻烦。

    推荐办法:
    运用继承QObject,结合MoveToThread()的方式:

    自定义my_serial类,继承自QObject,将一个QThread local_thread 对象作为派生类的成员,使用QObject的MoveToThread()将派生类自己以及QSerialPort对象都移动到local_thread线程中,这样派生类中的槽函数以及QSerialPort中的信号与槽函数都将在local_thread线程执行。

    1.解决跨线程调用
    既然无法通过指针的形式将QSerialPort传入子线程中,那么就只有在子线程中新建QSerialPort对象。

    显然,如果使用第一种多线程方式的话,由于只有run()函数内的代码是在子线程的,那么QSerialPort就只能在run函数中建立,如果在UI界面上有个点击发送数据的按钮的话,是无法将按钮点击事件传递到run()函数中的,显然,此种方法不合适。

    (1)主线程调用子线程内QSerialPort的发送数据函数发数据
    如果想点击按钮发送数据的话,可以这么做:主线程点击按钮后emit一个信号,将这个信号与子线程中的槽连接,在槽函数中调用QSerialPort的发送数据函数。

    (2)主线程显示子线程内QSerialPort收到的数据
    同样的,如果想将子线程里QSerialPort接收到的数据显示到UI界面上,可以在子线程QSerialPort收到数据后emit一个信号出来,将该信号和主线程里的槽函数连接,就实现了将数据传入到了主线程里,然后将数据显示到界面上就可以了。

    tips:其实严格意义上来讲,Qt的多线程就不是使用继承QThread这种方式,而是moveToThread方式。

    二、多线程使用的场景
    通常小的程序,没那么复杂的程序其实不需要使用多线程也能满足的,因为现在的计算机性能都很高了,差不多也够用了。但是在很多场景中我们还是得需要使用多线程的。

    1、处理耗时的程序的时候,或者需要等待、循环等操作的时候。

    2、在处理计算量比较大的代码的时候。

    以上两种如果不采用多线程的方式的话,会造成界面卡顿的现象,因为Qt默认都是在一个主线程中的,如果要提高程序的响应就必须要这样做。这一点要特别的留意。

    3、像一些C/S架构的软件,使用多线程并发去响应用户的请求。

    4、对于多核的cpu来说,还可以提高利用率。

    Qt中多线程要注意的事项
    其实,计算机中并不是线程用的越多越好,因为多线程会涉及到线程安全的问题,这个问题本文就不分享了。另外多线程的本质是时间片段的切换,只不过cpu处理的快,像是同时在处理。就算是多核的计算机也会涉及到状态切换等问题,这些管理都是需要消耗资源的。

    所以在Qt的编程中,如果不是迫不得已的情况下,一般的我们不建议线程数多于3个。

    同时,我也不建议把串口接收的类封装为一个多线程的操作,因为串口和网络这些收发数据都是异步的,操作系统会调度,完全没必要再去封装为一个多线程。把接收到的数据需要计算的,耗时处理的扔到另一个线程里,这才是我们应该考虑的事情。

    但是,有时候,我们迫不得已在串口或者网口接收数据后立马做一些操作,所以也会采用多线程的串口类。

    在此,再次建议大家,不要把串口、网口这些接收的类封装到多线程中。

    三、代码实现
    封装一个类的串口接收类。同时、TCP/UDP接收的类都可以这样封装。

    头文件如下:

    Class SerialRender : public QObject
    {
           Q_OBJECT
    public:
         explicit SerialRender( QObject *parent = 0 );
         ~SerialRender();
     
         void setSerialPort( QSerialPort* serialPort );
         void setPortName( QString portName );
         void setBaudRate(int baudRate);
         void setInitData();
    public slots:
         void openCommSlot(bool bOpenFlag);
         void readSerialPortData();
     
    private:
         QSerialPort* m_serialPort = NULL;
         QThread * m_thread;
         QString m_portName;
         int m_baudRate;
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    cpp文件如下:

     
        m_portName = portName ;
    }
     
    void SerialRender::setBaudRate(int baudRate)
    {
        m_baudRate = baudRate;
    }
     
    void SerialRender::setInitData()
    {
        m_thread = new QThread();
        this->moveToThread(m_thread);
        m_thread->start();
        connect( m_serialPort , SIGNAL( readyRead() ), this, SLOT(readSerialPortData())  );
    }
     
    void SerialRender::openCommSlot(bool bOpenFlag)
    {  
        if ( bOpenFlag ){
            m_serialPort->flush();
            m_serialPort->setPortName(m_portName);
            m_serialPort->open(QIODevice::ReadWrite);
            m_serialPort->setBaudRate(m_baudRate );
            m_serialPort->setDataBits(QSerialPort::Data8);
            m_serialPort->setParity(QSerialPort::NoParity);
            m_serialPort->setStopBits(QSerialPort::OneStop);
            if (m_serialPort->isOpen()){
                 qDebug()<<"打开成功";
            }else{
                 qDebug()<<"打开失败";
            }
        }else{
            m_serialPort->flush();
            m_serialPort->close();
        }
    }
     
    void SerialRender::readSerialPortData()
    {
        QByteArray data = m_serialPort->readAll();
     
        /***do something***/
    }
    
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    第二种方式
    qserialwork.h

    #ifndef QSERIALWORK_H
    #define QSERIALWORK_H
    
    #include 
    #include 
    
    #define QSERIALWORK_ERR_OK               0
    #define QSERIALWORK_ERR_OPEN_SUCCESS     1
    #define QSERIALWORK_ERR_OPEN_FAILED      2
    
    class QSerialWork : public QObject
    {
        Q_OBJECT
    public:
        explicit QSerialWork(QObject *parent = nullptr);
    
        ~QSerialWork();
    
        void InitSerialPort();
    
        bool isOpen();
    
    public slots:
    
        void OpenPort(QString portName);
    
        void ClosePort();
    
        void WriteData(const QByteArray& buf, qint64 len);
    
    private slots:
    
        void DataArrived();
    
    signals:
    
        void NewData(QByteArray data);
    
        void State(int err);
    
    private:
    
        QSerialPort* m_serialPort;
    
        bool m_isOpen;
    
    };
    
    #endif // QSERIALWORK_H
    
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    qserialwork.cpp

    #include "qserialwork.h"
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    QSerialWork::QSerialWork(QObject *parent) : QObject(parent)
    {
        qDebug() << __FUNCTION__ << "Thread ID:" << QThread::currentThreadId();
    
        m_isOpen = false; 
    }
    
    QSerialWork::~QSerialWork()
    {
        qDebug() << __FUNCTION__ << "Thread ID:" << QThread::currentThreadId();
    }
    
    void QSerialWork::InitSerialPort()
    {
        qDebug() << __FUNCTION__ << "Thread ID:" << QThread::currentThreadId();
    
        m_serialPort = new QSerialPort(this);
        connect(m_serialPort, &QSerialPort::readyRead, this, &QSerialWork::DataArrived);
    }
    
    bool QSerialWork::isOpen()
    {
        return m_isOpen;
    }
    
    void QSerialWork::OpenPort(QString portName)
    {
        qDebug() << __FUNCTION__ << "Thread ID:" << QThread::currentThreadId();
    
        m_serialPort->setPortName(portName);
        m_serialPort->setBaudRate(QSerialPort::Baud115200);
        m_serialPort->setDataBits(QSerialPort::Data8);
        m_serialPort->setParity(QSerialPort::NoParity);
        m_serialPort->setStopBits(QSerialPort::OneStop);
        m_serialPort->setFlowControl(QSerialPort::NoFlowControl);
    
        m_isOpen = m_serialPort->open(QSerialPort::ReadWrite);
    
        if(m_isOpen)
        {
            emit State(QSERIALWORK_ERR_OPEN_SUCCESS);
        }else
        {
            emit State(QSERIALWORK_ERR_OPEN_FAILED);
        }
    }
    
    void QSerialWork::ClosePort()
    {
        qDebug() << __FUNCTION__ << "Thread ID:" << QThread::currentThreadId();
    
        m_serialPort->close();
    }
    
    void QSerialWork::WriteData(const QByteArray& buf, qint64 len)
    {
        qDebug() << __FUNCTION__ << "Thread ID:" << QThread::currentThreadId();
    
        m_serialPort->write(buf, len);
    //    QString str = "fb200580ff020a00f8010000100000000100e71345040000fd0d0ffe";
    //    QByteArray send_buf = QByteArray::fromHex(str.toLatin1());
    //    emit NewData(send_buf);
    }
    
    void QSerialWork::DataArrived()
    {
        qDebug() << __FUNCTION__ << "Thread ID:" << QThread::currentThreadId();
    
        QByteArray data = m_serialPort->readAll();
    
        emit NewData(data);
    }
    
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
  • 相关阅读:
    linux ld 链接器学习笔记
    python基于PHP+MySQL的药店药品进销存管理系统
    找软件,用开源,就上alternativeto.net
    如何提高视频清晰度?视频调整清晰度操作方法
    docker入门(二)—— docker三大概念(镜像、容器、仓库)
    深入浅出MySQL-03-【MySQL中的运算符】
    保姆级阿里云ESC服务器安装nodejs和服务器node服务管理工具PM2安装使用
    Zookeeper 与分布式算法
    [附源码]java毕业设计暖暖猫窝系统
    SQL干货丨关于分组和聚合函数,如何实现查询排名?!
  • 原文地址:https://blog.csdn.net/m0_46577050/article/details/133483158