[C++ QT项目实战]----C++ QT系统实现多线程通信

前言

        在C++ QT中,多线程通信原理主要涉及到信号与槽机制和事件循环机制。

        1、信号与槽机制:
        在QT中,信号与槽是一种用于对象间通信的机制。对象可以通过发送信号来通知其他对象,其他对象通过连接槽来接收信号并进行相应的处理。这个机制可以跨线程使用,即一个线程可以发送信号,另一个线程可以接收信号。

        在使用多线程时,通过将一个对象的信号与另一个对象的槽连接起来,可以在不同线程中进行通信。当信号发出时,QT会在接收对象所在的线程中调用槽函数,从而实现多线程之间的通信。

        2、事件循环机制:
        在QT中,每个线程都有自己的事件循环。事件循环是处理事件的机制,包括用户输入、定时器事件等。当一个线程启动时,会进入事件循环,等待事件发生并处理事件。

        在多线程通信中,通常会涉及到在不同线程之间传递事件。通过在一个线程中发出事件,另一个线程中的事件循环可以接收并处理这个事件,从而实现线程间的通信。

        总的来说,C++ QT中的多线程通信原理是通过信号与槽机制和事件循环机制来实现的。通过使用这两种机制,可以在不同线程之间进行通信,并实现多线程之间的协作。但需要注意线程安全性,避免出现竞态条件和线程安全问题。

正文

01-功能演示

        如下图所示,第一张图,是经过运行之后,进入主界面,会同时显示后台信息,黑色的控制台界面就是线程运行信息,是否有两个线程也可以查看,主界面可以通过控件控制进程运行状态。        

        注:系统资源竞态是指多个线程或进程在对共享资源进行访问时,由于执行顺序不确定或未正确同步,导致竞争条件发生,从而导致数据不一致或程序出现错误的现象。

        当多个线程同时访问共享资源时,如果没有使用适当的同步机制来确保每个线程对资源的访问顺序,就可能引发系统资源竞态。系统资源竞态会导致一些严重问题,包括但不限于以下几点:

        数据不一致性:如果多个线程同时访问共享数据但没有正确同步,可能会导致数据不一致的情况。例如,一个线程正在修改数据的同时,另一个线程也在读取或修改相同的数据,可能会导致数据错误或不一致。

        资源泄漏:当多个线程同时竞争资源而没有正确释放资源,可能会导致资源泄漏。例如,并发写入文件时,如果某个线程在写入文件后未正确关闭文件句柄,可能会导致文件描述符泄漏。

        死锁:系统资源竞态还可能导致死锁的发生。当两个或多个线程相互等待对方释放资源时,可能导致所有参与的线程无法继续正常执行,从而导致程序挂起。

        因此,在采用系统多线程运行时,一定要使用一些方法防止资源竞争,可以采取以下几种措施:a、使用互斥量(Mutex)或信号量(Semaphore)等同步机制,在访问共享资源时确保只有一个线程可以访问;b、 使用临界区(Critical Section)来限制对共享资源的访问,确保线程之间的同步;c、使用条件变量(Condition Variable)在需要等待某个条件满足时进行线程等待和唤醒。

        这里的系统使用的是数据库进行资源交换和读取等,线程之间互不干扰,不存在线程竞争问题。

         下面是主线程和子线程之间通信的几个案例,也是系统中运行产生的,如下图所示,系统改为手动模式,首先点击打开配置文件按钮,可以看出控制台运行信息为“配置文件config.ini已打开!”,点击平台数据读取按钮,显示下方运行信息,这就是主线程和子线程之间的交互信息,可以通过QT中信号与槽函数的形式实现。

        下面重点分析如何实现,并附代码解释。

02-系统多线程实现

        这里讲解四个实现函数文件,其实是两个文件:mainwindow.h和mainwindow.cpp文件为主线程文件Zanj_WFC_ctrl.h和Zanj_WFC_ctrl.cpp文件为子线程文件,下面一一解释。

        mainwindow.cpp文件

        (1)、该文件用于实现显示主线程中的主界面,也就是运行之后,第一个界面,可以在界面上进行操作,后台信息是由于spdlog日志信息的导入,才会进行信息显示,便于观察,#include "spdlog/spdlog.h",代码中下面这一部分就是建立子线程的核心,QT中可以使用moveToThread()函数建立新线程。具体介绍如下:

        a、m_Zanj_WFC_ctrl = new Zanj_WFC_ctrl;// 这个实例要负责预报计算任务
        b、m_Zanj_WFC_ctrl_Thread = new QThread; // 子线程,本身不负责预报计算
        c、connectZanj_WFC_ctrl(m_Zanj_WFC_ctrl); // 连接信号-槽,复制的开始和取消指令是通过信号发送的
        d、m_Zanj_WFC_ctrl->moveToThread(m_Zanj_WFC_ctrl_Thread);// 将实例移动到新的线程,实现多线程运行
        e、m_Zanj_WFC_ctrl_Thread->start();

        (2)、函数connectZanj_WFC_ctrl()用于连接子线程函数,里面有该函数实现的功能,使用connect()函数编写代码: connect(this, SIGNAL(system_predict_init_link()), m_Zanj_WFC_ctrl, SLOT(HDGL_SYSTEM_Init()));这个函数HDGL_SYSTEM_Init()在子线程中实现,也就是说只要主线程启动,子线程也会跟着启动,可以在该函数中实现子线程的功能。

        (3)、上述描述的主线程与子线程通信的案例中的实现过程如下:也是通过信号与槽函数方式,比如打开配置文件函数,首先对该函数进行实现on_Open_config_Btn_clicked(),该函数就是打开配置文件控件对应的函数,该函数实现了点击按钮之后,发送了信号emit send_config();该信号通过connect()函数发送到子线程,代码为connect(this, &MainWindow::send_config, m_Zanj_WFC_ctrl, &Zanj_WFC_ctrl::OnconfigSlot);OnconfigSlot()函数在子线程中实现。

        原理就是这样,虽有些繁琐,但是逻辑性强。另一个读取数据按钮原理一样。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "Zanj_WFC_ctrl.h"
#include "qdebug.h"
#include <qstring.h>#include "cglobal.h"
#include <QElapsedTimer>
#include <iostream>
#include "windows.h"
#pragma comment(lib,".\\lib\\fmtd.lib")#include "spdlog/fmt/ostr.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/rotating_file_sink.h"#include  <QTimer>
#include  <time.h>
#include  <QLabel>
#include  <QValueAxis>
#include  <QMargins>
#include  <QtWidgets/QApplication>
#include  <QtWidgets/QMainWindow>#include <ctime>  using namespace std::literals;
using namespace std;
namespace spd = spdlog;
auto console_mainWindow = spd::stdout_color_mt("主界面控制台");
auto rotating_logger_mainWindow = spdlog::rotating_logger_mt("CAO8_Winmain_rotating", "logs/CAO8_rotating.logger", 1048576 * 5, 12);MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);setWindowTitle(QString::fromLocal8Bit("主系统")); // 此处写应用程序在标题栏上显示的名称setStyleSheet("background - color:pink; ");reshow();data_log_tblview();console_mainWindow->info("欢迎进入主系统UI线程[Mainwindow]!");rotating_logger_mainWindow->info("欢迎进入主系统UI线程[Mainwindow]!");m_Zanj_WFC_ctrl = new Zanj_WFC_ctrl;// 这个实例要负责预报计算任务m_Zanj_WFC_ctrl_Thread = new QThread; // 子线程,本身不负责预报计算connectZanj_WFC_ctrl(m_Zanj_WFC_ctrl); // 连接信号-槽,复制的开始和取消指令是通过信号发送的m_Zanj_WFC_ctrl->moveToThread(m_Zanj_WFC_ctrl_Thread);// 将实例移动到新的线程,实现多线程运行m_Zanj_WFC_ctrl_Thread->start();emit system_predict_init_link();// 向子线程发送打开配置文件信息connect(this, &MainWindow::send_config, m_Zanj_WFC_ctrl, &Zanj_WFC_ctrl::OnconfigSlot);// 向子线程发送读取数据信号,子线程接收后,进行数据读取connect(this, &MainWindow::send_rChk_ToWFC, m_Zanj_WFC_ctrl, &Zanj_WFC_ctrl::OnGETSlot);}MainWindow::~MainWindow()
{delete ui;
}// 这里连接子线程,进行子线程的开启,子线程连接数据库,连接大数据平台,打开所需文件的信号传递,以及MainWindow界面槽函数处理信号的实现
void MainWindow::connectZanj_WFC_ctrl(Zanj_WFC_ctrl * m_Zanj_WFC_ctrl)
{connect(this, SIGNAL(system_predict_init_link()), m_Zanj_WFC_ctrl, SLOT(HDGL_SYSTEM_Init()));// 使用信号-槽机制,发出开始指令}// 打开配置文件函数
void MainWindow::on_Open_config_Btn_clicked()
{ui->Iplate4j_IP_QLE->setText("127.0.0.1");ui->model_PC_IP_QLE->setText("127.0.0.1");emit send_config();
}// 由主窗口发送数据读取信号,子线程实现数据读取操作
void MainWindow::on_Restful_data_read_Btn_clicked()
{emit send_rChk_ToWFC();}
        mainwindow.h文件

        该文件就是.cpp文件中众多函数的声明,以及信号的声明,比如读取数据时发送的信号需要在这里声明一下,便于使用。Zanj_WFC_ctrl * m_Zanj_WFC_ctrl; // 预报计算的类
    QThread * m_Zanj_WFC_ctrl_Thread; //将被移动到此线程执行
,这里也定义了指针,用于在.cpp文件中使用。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include"ui_mainwindow.h"
#include <QMainWindow>
#include <QLabel>
#include <QtSql/QtSql>#include "spdlog/fmt/ostr.h"
#include "Zanj_WFC_ctrl.h"
#include <QVariant>
#include "wfc_to_main.h"#include <QtSql/QtSql>
#include <QDateTime>
#include <QTimer>
#include <qtextcodec.h> 
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>#include <QMouseEvent>
//一定要声明!!!
#include"dialog_data_log.h"
//#include<vector>//#include "dialog_data_log.h"
namespace Ui {class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();Zanj_WFC_ctrl * m_Zanj_WFC_ctrl; // 预报计算的类QThread * m_Zanj_WFC_ctrl_Thread; //m_Mp_Pred将被移动到此线程执行void connectZanj_WFC_ctrl(Zanj_WFC_ctrl *m_Zanj_WFC_ctrl);private slots :void on_Restful_data_read_Btn_clicked();   //点击平台数据读取按钮,实现读取数据信号发送	void on_Open_config_Btn_clicked();signals:void system_predict_init_link();//定义向子线程发送的信号,用于实现子线程开启void send_config();      // 定义向子线程发送打开配置文件的信号void send_rChk_ToWFC();  // 定义向子线程发送数据读取的信号private:Ui::MainWindow *ui;QLabel * pLabel;};#endif // MAINWINDOW_H
        Zanj_WFC_ctrl.cpp文件

        (1)、该文件中的众多函数就是对主线程发送的信号的实现过程,当接受到主线程的信号之后,如果需要对其进行处理,便可以在这里定义函数实现过程,该HDGL_SYSTEM_Init()函数就是对主线程中connectZanj_WFC_ctrl()连接函数的实现。

         (2)、案例中主线程发送读取数据信号,子线程中函数的实现OnGETSlot()在该文件中,这里适用于读取数据函数的实现,可以根据需要进行修改。打开配置文件的实现原理与之一致。

#include "Zanj_WFC_ctrl.h"
#include "wfc_to_main.h"
#include <Python.h>
#include "qdebug.h"
#include <qstring.h>
#include <Python.h>#pragma comment(lib,"mclmcrrt.lib")  
#pragma comment(lib,"libmx.lib")  
#pragma comment(lib,"libmat.lib")  
#pragma comment(lib,"mclmcr.lib")  
#pragma comment(lib,".\\lib\\CGL_Mech_Property_BP_pred.lib")
#pragma comment(lib,".\\lib\\fmtd.lib")#include <QAxObject>
#include <QStandardPaths>
#include <QEventLoop>
#include <QDesktopWidget>#include <algorithm>
#include <numeric>
#include <vector>
#include <QVector>
#include <stdlib.h>#include "spdlog/fmt/ostr.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/rotating_file_sink.h"using namespace libxl;
using namespace std::literals;
//using namespace fmt;
using namespace std;
namespace spd = spdlog;
auto console_WFC_thread = spd::stdout_color_mt("WFC计算线程 ");
auto rlogger_WFC_thread = spdlog::rotating_logger_mt("CAO8_WFC_thread_rotating", "logs/CAO8_rotating.logger", 1048576 * 5, 12);int Zanj_WFC_ctrl::HDGL_SYSTEM_Init()
{rlogger_WFC_thread->flush_on(spd::level::debug);console_WFC_thread->info("欢迎进入主系统计算线程");str_info = QString::fromLocal8Bit("0 应用程序环境路径  ") + path_deploy;return 1;
}void Zanj_WFC_ctrl::OnGETSlot()
{QString strMessage;QString strResult;QString strUrl;QString strInput;QFile file(QApplication::applicationDirPath() + "/1.json");if (!file.open(QIODevice::WriteOnly)) {console_WFC_thread->error("Json file open failed! ");rlogger_WFC_thread->error("Json file open failed! ");}else {console_WFC_thread->info("Json file open successfully! ");rlogger_WFC_thread->info("Json File open successfully! ");}//QString strInput = ui.textEdit_input->toPlainText();//strUrl += "http://" + ui.lineEdit_Url->text();strUrl = serverIP + serviceID_get;strInput = clientSecret;SendAndGetText(strUrl, get1, strInput, strMessage, strResult);QJsonParseError jsonError;QByteArray byteResult = strResult.toUtf8();//QString转QByteArray//QJsonDocument json = QJsonDocument::fromJson(byteResult, &jsonError);json_doc_get = QJsonDocument::fromJson(byteResult, &jsonError);if (json_doc_get.isNull()){console_WFC_thread->critical("json文件为空文档");//console->critical("jsonError错误类型" + jsonError.errorString().toStdString());//qDebug() << jsonError.errorString();rlogger_WFC_thread->critical("json格式错误[json_doc_get_slot]");//qDebug() << "json格式错误";//注意:如果文档中有中文,就会报格式错误}else if (jsonError.error != QJsonParseError::NoError){console_WFC_thread->critical("json格式错误[json_doc_get_slot]" + jsonError.errorString().toStdString());//qDebug() << jsonError.errorString();rlogger_WFC_thread->critical("json格式错误[json_doc_get_slot]");}else {if (json_doc_get.isObject()) {doc_get_Obj = json_doc_get.object();}}json_doc_get_parse();
//	RT_Iplat4j_Communication();  //放在这里可以在主窗口点击数据读取按钮之后,才会提示是否数据平台连接成功file.write(json_doc_get.toJson(QJsonDocument::Indented)); //Indented:表示自动添加/n回车符file.close();}void Zanj_WFC_ctrl::OnconfigSlot()
{configuration_read();
}
         Zanj_WFC_ctrl.h文件

        该文件也是.cpp文件中众多函数的声明,比如处理读取数据时发送的信号的函数需要在这里声明一下,便于使用。

#pragma once#include <QObject>
#include <QtSql/QtSql>
#include <QVariant>//#include <connection.h>
//#include <QSql>
#include <QSqlError>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QDebug>
#include <QMessageBox>
#include <QEventLoop>
#include <qstring.h>
#include <string>
#include "snap7.h"
#include <QtCore/QJsonObject>
#include <QtCore/QJsonDocument>
#include <Python.h>class Zanj_WFC_ctrl : public QObject
{Q_OBJECTpublic:Zanj_WFC_ctrl(QObject *parent=0);~Zanj_WFC_ctrl();public slots:int HDGL_SYSTEM_Init();void OnGETSlot();void OnconfigSlot();

总结

     同时运行的两个线程,与单线程相比:

        提高系统性能:双线程可以并行执行,可以更充分地利用多核处理器的资源,从而提高系统的整体性能。

        增加程序响应速度:通过将一些耗时的操作放在单独的线程中执行,可以避免阻塞主线程,提高程序的响应速度和用户体验。

        提高系统的并发性:双线程可以同时处理不同的任务,增加系统的并发性,使系统更具有高效性和灵活性。

        分解复杂任务:通过将复杂任务拆分成多个线程来执行,可以更容易地管理和维护代码,提高代码的可读性和可维护性。

        改善软件的稳定性:通过将不同功能模块分别放在不同的线程中执行,可以避免由于一个线程的错误导致整个系统崩溃的情况,提高软件的稳定性。

        总的来说,双线程可以提高系统的性能、响应速度和并发性,同时也能改善软件的稳定性和可维护性,是一种常用的提升程序效率的方法。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序:12.页面导航

什么是页面导航 页面导航指的是页面之间的相互跳转。例如&#xff0c;浏览器中实现的页面导航的方式有两种&#xff1a; 连接location.href 小程序中实现页面导航的两种方式 声明式导航 在页面上声明一个导航组件 通过点击组件实现页面跳转 导航TabBar页面 是指配置TabB…

mac 教程 终端如何拆墙

一直觉得自己写的不是技术&#xff0c;而是情怀&#xff0c;一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的&#xff0c;希望我的这条路能让你们少走弯路&#xff0c;希望我能帮你们抹去知识的蒙尘&#xff0c;希望我能帮你们理清知识的脉络&#xff0…

C语言:一维数组、二维数组、字符数组介绍

数组 介绍一维数组定义应用方法初始化 举例示例结果 二维数组定义应用方法初始化 举例示例结果 字符数组定义应用方法初始化 举例示例结果分析 介绍 在C语言中&#xff0c;数组是一种基本的数据结构&#xff0c;用于存储一系列相同类型的数据。数组可以是多维的&#xff0c;最…

Android --- 网络请求

通常在 Android 中进行网络连接一般使用 Scoket 和HTTP&#xff0c;HTTP 请求方式比 Scoket 多。HTTP 请求一般采用原生的 HttpClient 和 HttpUrlConnection 的两种网络访问方式&#xff08;系统自带的&#xff09;。但是在 Android 5.0 的时候 Google 就不推荐使用 HttpClient…

Bytebase 2.16.0 - 支持 Oracle 和 SQL Server DML 变更的事前备份

&#x1f680; 新功能 支持 Oracle 和 SQL Server DML 变更的事前备份。 支持在 SQL 编辑器中显示存储过程和函数。 支持兼容 TDSQL 的 MySQL 和 PostgreSQL 版本。 支持把数据库密码存储在 AWS Secrets Manager 和 GCP Secret Manager。 支持通过 IAM 连接到 Google Clou…

Java | Leetcode Java题解之第52题N皇后II

题目&#xff1a; 题解&#xff1a; class Solution {public int totalNQueens(int n) {Set<Integer> columns new HashSet<Integer>();Set<Integer> diagonals1 new HashSet<Integer>();Set<Integer> diagonals2 new HashSet<Integer>…

ArcGIS小技巧—一文带你理清个人地理数据库和文件地理数据库

不知各位GISer在使用Arcgis软件时是否会遇到这样一个问题&#xff0c;在新建一个地理数据库来存放要素数据集时会有文件地理数据库和个人地理数据库两种&#xff0c;那么&#xff0c;这两种地理数据库有何区别呢&#xff1f; 首先&#xff0c;我们先来看看地理数据库的定义&…

Thread方法具体解析

对于run方法 如果该线程是使用单独的 Runnable run 对象构造的&#xff0c;则调用该 Runnable 对象的 run 方法&#xff1b;否则&#xff0c;此方法不执行任何操作并返回。 对于start方法 导致该线程开始执行&#xff1b; Java虚拟机调用该线程的run方法。 这里介绍一个快捷键…

【OceanBase诊断调优 】—— 如何快速定位SQL问题

作者简介&#xff1a; 花名&#xff1a;洪波&#xff0c;OceanBase 数据库解决方案架构师&#xff0c;目前负责 OceanBase 数据库在各大型互联网公司及企事业单位的落地与技术指导&#xff0c;曾就职于互联网大厂和金融科技公司&#xff0c;主导过多项数据库升级、迁移、国产化…

c++map类operator[]详解

定义 让我们来看一下标准库里对该函数的定义. map::operator[] - C Reference (cplusplus.com) 参数是一个key_val类型的值,返回值是对应key 的value值,如果key不存在的话,value会调用其默认构造函数来初始化. 使用 举个例子 int main() {map<string, string> dict{ {…

基于springboot的高校宣讲会管理系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

Ubuntu解密:Root账户登录问题一网打尽

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Ubuntu解密&#xff1a;Root账户登录问题一网打尽 前言Root用户简介Root账户无法登录的原因重设Root账户密码解决ssh不能root登录问题安全性考虑 前言 Ubuntu作为广受欢迎的Linux发行版&#xff0c;对…