【QT常用技术讲解】多线程处理+全局变量处理异步事件并获取多个线程返回的结果

前言

       QTableView加入勾选项后(参考【QT常用技术讲解】QTableView添加QCheckBox、QPushButton),如果支持右键菜单功能,此时就有统一执行多个异步事件,并且统一输出到界面的需求了,本篇结合多线程+共享全局变量进行开发。

概述

        QT开发中,单个异步事件处理(比如网络通信ping一个IP,或者是执行一条后台命令后等待返回结果),创建一个线程,并通过connect关联信号和槽,异步获取到一条结果即可,如果是多个同种类型异步事件需要同时处理(比如网络通信ping多个IP,或者是执行多条后台命令后等待返回结果),此时就需要创建多个线程,并且用共享全局变量收集全部结果进行统一处理。

        本篇通过同时开多个线程ping IP,并获取全部结果后,通过对话框展示结果来实现整体功能。

功能详解

1、增加共享全局变量

增加了两个文件:globalvalue.h和globalvalue.cpp,对变量进行声明和定义,代码如下所示:

//globalvalue.h源代码
#ifndef GLOBALVALUE_H
#define GLOBALVALUE_H
#include <QString>
#include <QList>

namespace GlobalVariables {//声明全局变量
    extern QList<QString> g_calbackmsg;
}

#endif // GLOBALVALUE_H
//globalvalue.cpp源代码
#include "globalvalue.h"

namespace GlobalVariables {//定义全局变量
    QList<QString> g_calbackmsg;
}

2、改造命令处理模块

原来是单线程时,后台命令处理线程完成任务之后,通过发送信号 emit callback()把结果输出给PING模块即可,而多条命令时,则通过互斥锁+全局共享变量获取返回结果来处理。

    //多线程调用共享变量,需要上互斥锁
    mutex.lock();
    //qDebug() << __LINE__ << "add ret:"<<retstr;
    g_calbackmsg.append(retstr);
    mutex.unlock();

完整代码如下:

//threadCmd.cpp源代码
#ifndef THREADCMD_CPP
#define THREADCMD_CPP

#include "threadCmd.h"
#include <QDebug>
#include <QProcess>
#include "src/util/comm_define.h"
#include <QTextCodec>
//增加全局变量头文件和命名空间,以及互斥锁
#include <QMutex>
#include "src/util/globalvalue.h"
using namespace GlobalVariables;
QMutex mutex;

ThreadCmd::ThreadCmd(const QString &param,QObject *parent) : QThread(parent),m_param(param) {

}

void ThreadCmd::run(){
    QString retstr="";
    QProcess process;
    // 执行命令
    QString cmd = m_param;
    //qDebug() << __LINE__ << cmd;
    process.start(cmd);
    if(process.waitForFinished()){
        // 读取进程的输出
        QString output = QString::fromLocal8Bit(process.readAllStandardOutput());
        QTextCodec *codec = QTextCodec::codecForName("GBK");
        QString unicodeOutput = codec->toUnicode(output.toLocal8Bit());
        //emit callback(unicodeOutput);
        retstr=QString("[error]:命令[%1]命令执行成功").arg(cmd);
    }else{
        // emit callback("[error]:命令执行失败");
        retstr=QString("[error]:命令[%1]命令执行错误").arg(cmd);
    }
    //多线程调用共享变量,需要上互斥锁
    mutex.lock();
    //qDebug() << __LINE__ << "add ret:"<<retstr;
    g_calbackmsg.append(retstr);
    mutex.unlock();
    //emit callback(retstr);----多线程用了共享全局变量之后,不需要返回此值
    // 进程使用完毕后,可以手动删除
    process.deleteLater();
    exit(0);
}


#endif // THREADCMD_CPP

3、ping模块多线程改造

对按钮事件进行改造,创建多线程之前,通过g_calbackmsg.clear()先请客全局共享变量,创建线程后,增加了多线程列表threadList来启动和删除线程,另外单独增加一个线程来处理结果ThreadDisplaymsg,代码如下所示:

//pingdialog.cpp源代码
void pingDialog::on_start_clicked()
{
    bool bfind=false;
    ui->textBrowser->setReadOnly(false);//
    ui->textBrowser->setText("--------ping start--------");
    QString times = ui->timeBox->currentText();
    QString Ipstr=ui->ipEdit->text();
    //处理多个IP,组装后台执行命令,并调用线程取执行
    g_calbackmsg.clear();
    QStringList iplist = Ipstr.split(";");
    for(int  i=0;i<iplist.size();i++){
        QString ip=iplist[i];
        QString cmd = QString("ping -n %1 %2").arg(times).arg(ip);
        qDebug() << __LINE__ <<"cmd:" <<cmd;
        //把调用QProcess执行后台命令的代码改成调用多线程类
        ThreadCmd *thread = new ThreadCmd(cmd, this);
        //等待多线程的callback信号,关联ThreadsendResult槽函数来处理结果
        connect(thread, &ThreadCmd::callback, this, &pingDialog::ThreadsendResult);
        threadList.append(thread);
        thread->start();
        bfind = true;
    }
    qDebug()<< __LINE__ << __FUNCTION__ << bfind;
    if(bfind){
        //创建专门的结果处理线程来弹窗对话框显示结果
        ThreadDisplaymsg *dthread = new ThreadDisplaymsg(iplist,this);
        connect(dthread, &ThreadDisplaymsg::callback, this, &pingDialog::DisplayResult);
        dthread->start();

    }
    this->close();

}

4、新增结果处理线程ThreadDisplaymsg

增加了两个文件:threadDisplaymsg.h和threadDisplaymsg.cpp,

//threadDisplaymsg.h源代码
#ifndef THREADDISPLAYMSG_H
#define THREADDISPLAYMSG_H

#include <QThread>
#include <QString>
#include "src/util/globalvalue.h"  //引用全局变量的头文件
using namespace GlobalVariables;   //声明全局变量的命名空间

class ThreadDisplaymsg : public QThread
{
	Q_OBJECT
public:
    explicit ThreadDisplaymsg(const QStringList &param,QObject *parent = nullptr) ;

protected:
    void run() override;
signals:
    void callback(const QString result);
private:
    QStringList m_paramlist;
};
 
#endif // THREADDISPLAYMSG_H
//threadDisplaymsg.cpp源代码
#ifndef THREADDISPLAYMSG_CPP
#define THREADDISPLAYMSG_CPP

#include "threadDisplaymsg.h"
#include <QDebug>
#include <QMessageBox>

//传递参数param进来
ThreadDisplaymsg::ThreadDisplaymsg(const QStringList &param,QObject *parent) : QThread(parent),m_paramlist(param) {

}

void ThreadDisplaymsg::run(){
    int len = m_paramlist.size();//m_paramlist是IP列表,即IP数量,也等于开启线程的数量
    //qDebug() << "ThreadDisplaymsg:" <<__LINE__ << len;
    int loop_num=0;
    int max_num=100;//如果超过100秒还没获取到结果,说明你要优化的是异步的响应机制,而不是当前的代码,100秒都给不了结果,体验得有多差
    while(loop_num<max_num){//等待全部的线程返回结果
        if(g_calbackmsg.size()==len)//结果数量
            break;
        loop_num++;
        QThread::sleep(1);
    }
    qDebug() << "ThreadDisplaymsg wait for " <<loop_num << "s";
    QString result="";
    for(QString msg : g_calbackmsg) {
        result += msg;
        result += "\n";
    }
    qDebug() << "=====ThreadDisplaymsg callback=======";
    //所有的结果累加完成后发送信号callback
    emit callback(result);
}

#endif // THREADDISPLAYMSG_CPP

5、QTableView表的模块改动

        改动位置是右键菜单功能的信号和槽进行优化,增加了disconnect()来断掉connect()的链接,如下代码,如果注释掉disconnect()功能,每次点击ping功能菜单之后,disconnect()就会累加1,导致对话框弹出N个,不能等系统自动回收,需要disconnect()关掉,并且是在connect()之前关掉,因为多线程是异步的:先connect后disconnect的话,多线程还没返回结果,已经执行disconnect了,这时候connect就被关闭了。

void tab_basemsg::tableWidget_MenuRequested(const QPoint &pos) {
    QModelIndex index = ui->tableWidget->indexAt(pos);//获取当前行
    if (!index.isValid()) return;

    QMenu menu(this);
    //单选项才有查看详情
    if(m_iplist.size()<=1){
        QAction *DiplaymsgAction = menu.addAction(tr("查看详情"));
        connect(DiplaymsgAction,&QAction::triggered,[=](){
            //获取选择的单元格
            QList<QTableWidgetItem *> selected_cells = ui->tableWidget->selectedItems();
            if(!selected_cells.isEmpty()){
                QTableWidgetItem *codeCell = ui->tableWidget->item(selected_cells.first()->row(),HEAD_BASEMSG_CODE);
                if(codeCell!=nullptr){
                    QString code = codeCell->text();
                    stBasemsg basemsg=m_basemsgmap[code];
                    basemsgDialg->setModal(false);
                    basemsgDialg->setWindowTitle("保存");
                    //basemsgDialg->setFixedSize(500,400);
                    basemsgDialg->open();
                    basemsgDialg->init(1,basemsg);
                    basemsgDialg->exec();
                }
            }
        });
    }
    QAction *NetpingAction = menu.addAction(tr("Ping此计算机"));
    connect(NetpingAction,&QAction::triggered,[=](){
        QString ipstr;
        for(int  i=0;i<m_iplist.size();i++){
            QString ip=m_iplist[i];
            ipstr += ip;
            if(i!=(m_iplist.size()-1)){
                ipstr += ";";
            }
        }
        qDebug() << __LINE__ << __FUNCTION__ << ipstr;
        pingdlg->setIp(ipstr);
        pingdlg->setModal(false);
        pingdlg->setWindowTitle("PING测试");
        pingdlg->setFixedSize(500,400);
        pingdlg->open();
        pingdlg->init();
        pingdlg->exec();
        // 先断开之前的连接--如果不断开链接,每次操作此功能时,connect都+1,就会出现多次弹窗
        disconnect(pingdlg, &pingDialog::sendtobasemsg, this, nullptr);
        //connect要放在disconnect后面:即先关闭上一次的connect,再出现创建connect
        //因为多线程是异步的:先connect后disconnect的话,多线程还没返回结果,已经执行disconnect了,这时候connect就被关闭了
        connect(pingdlg,&pingDialog::sendtobasemsg,this,&tab_basemsg::displaysendFileresult);

    });

    menu.exec(QCursor::pos());
    //menu.exec(ui->tableWidget->mapToGlobal(pos));
}

篇尾

        全局变量是多线程中比较轻量级的共享机制,有很多成熟的消息传递技术,如果你的功能带有业务性,建议多调研选型,比如MQ中间件是典型的消息队列,技术上是典型的生产者<-->消费者的订阅/分发模型,可以减轻很大开发工作,如果觉得MQ中间件不好管理,可以自己开发环形队列来处理消息。

本资源设置1个资源分,您可以下载作为捐献。 如果您有Git,还可以从http://www.goldenhawking.org:3000/goldenhawking/zoom.pipeline直接签出最新版本 (上一个版本“一种可伸缩的全异步C/S架构服务器实现”是有问题的,现在已经完成更改)。 服务由以下几个模块组成. 1、 网络传输模块。负责管理用于监听、传输的套接字,控制数据流在不同线程中流动。数据收发由一定规模的线程池负责,实现方法完全得益于Qt线程事件循环。被绑定到某个Qthread上的Qobject对象,其信号-槽事件循环由该线程负责。这样,便可方便的指定某个套接字对象使用的线程。同样,受惠于Qt的良好封装,直接支持Tcp套接字及SSL套接字,且在运行时可动态调整。(注:编译这个模块需要Qt的SSL支持,即在 configure 时加入 -openssl 选项) 2、 任务流水线模块。负责数据的处理。在计算密集型的应用中,数据处理负荷较重,需要和网络传输划分开。基于普通线程池的处理模式,也存在队列阻塞的问题——若干个客户端请求的耗时操作,阻塞了其他客户端的响应,哪怕其他客户端的请求很短时间就能处理完毕,也必须排队等待。采用流水线线程池避免了这个问题。每个客户端把需要做的操作进行粒度化,在一个环形的队列中,线程池对单个客户端,每次仅处理一个粒度单位的任务。单个粒度单位完成后,该客户端的剩余任务便被重新插入到队列尾部。这个机制保证了客户端的整体延迟较小。 3、 服务集群管理模块。该模块使用了网络传输模块、任务流水线模块的功能,实现了跨进程的服务器ßà服务器链路。在高速局域网中,连接是快速、稳定的。因此,该模块被设计成一种星型无中心网络。任意新增服务器节点选择现有服务器集群中的任意一个节点,接入后,通过广播自动与其他服务器节点建立点对点连接。本模块只是提供一个服务器到服务器的通信隧道,不负责具体通信内容的解译。对传输内容的控制,由具体应用决定。 4、 数据库管理模块。该模块基于Qt的插件式数据库封装QtSql。数据库被作为资源管理,支持在多线程的条件下,使用数据库资源。 5、 框架界面。尽管常见的服务运行时表现为一个后台进程,但为了更好的演示服务器的功能,避免繁琐的配置,还是需要一个图形界面来显示状态、设置参数。本范例中,界面负责轮训服务器的各个状态,设置参数。设置好的参数被存储在一个ini文件中,在服务开启时加载。 6、应用专有部分模块。上述1-4共四个主要模块均是通用的。他们互相之间没有形成联系,仅仅是作为一种资源存在于程序的运行时(Runtime)之中。应用专有部分模块根据具体任务需求,灵活的使用上述资源,以实现功能。在范例代码中,实现了一种点对点的转发机制。演示者虚拟出一些工业设备,以及一些操作员使用的客户端软件。设备与客户端软件在成功认证登录后,需要交换数据。改变这个模块的代码,即可实现自己的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

deepallin

打赏叫号~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值