[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()函数在子线程中实现。

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

cpp 复制代码
#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文件中使用。

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_OBJECT

public:
	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()**在该文件中,这里适用于读取数据函数的实现,可以根据需要进行修改。打开配置文件的实现原理与之一致。

cpp 复制代码
#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文件中众多函数的声明,比如处理读取数据时发送的信号的函数需要在这里声明一下,便于使用。

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_OBJECT

	
public:
	Zanj_WFC_ctrl(QObject *parent=0);
	~Zanj_WFC_ctrl();

public slots:

	int HDGL_SYSTEM_Init();

	void OnGETSlot();
	
	void OnconfigSlot();
	

总结

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

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

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

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

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

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

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

相关推荐
weixin_514548893 分钟前
机器学习课程学习周报十五
人工智能·学习·机器学习
大拇指的约定4 分钟前
数据库(MySQL):使用命令从零开始在Navicat创建一个数据库及其数据表(三),单表查询
数据库·mysql·oracle
小码农<^_^>5 分钟前
c++继承(下)
开发语言·c++
非著名架构师8 分钟前
js混淆的方式方法
开发语言·javascript·ecmascript
Themberfue9 分钟前
基础算法之双指针--Java实现(下)--LeetCode题解:有效三角形的个数-查找总价格为目标值的两个商品-三数之和-四数之和
java·开发语言·学习·算法·leetcode·双指针
AIGC破防黑吗喽10 分钟前
Midjourney零基础学习
人工智能·gpt·学习·ai·stable diffusion·midjourney·ai绘画
深山夕照深秋雨mo18 分钟前
在Java中操作Redis
java·开发语言·redis
阳光阿盖尔19 分钟前
redis——哨兵机制
数据库·redis·缓存·主从复制·哨兵
小小娥子20 分钟前
【Redis】Hash类型的常用命令
数据库·spring boot·redis
盒马盒马20 分钟前
Redis:cpp.redis++通用接口
数据库·c++·redis