Qt线程/进程间数据通讯
1. Qt线程间通讯,主要应用场景是不同线程之间,传递数据,可使用全局变量,或者信号槽来进行数据的传递;
2. Qt进程间通讯,主要应用场景是使用Qt编写的程序之间相互通信;
Qt线程间数据通讯
方法一 全局变量
优点:使用方便;
缺点:全局变量长时间占用内存,影响程序空间使用率,且全局变量修改影响整个程序,程序的安全性无法保证;
注意:使用时需根据实际状况,加入锁,防止多线程同时使用同一个变量导致异常;
方法二 信号槽
只有QObject类及其派生的类才能使用信号和槽的机制,在线程间使用信号槽进行通信时,槽函数必须使用元数据类型的参数;如果使用自定义的数据类型,需要在connect之前将其注册(qRegisterMetaType)为元数据类型。
下面例子为使用自定义的数据类型作为信号槽传递的参数,使用方法如下:
------------------------------------------------------------------
// thread1.h
#ifndef THREAD1_H
#define THREAD1_H
#include <QDebug>
#include <QObject>
#include <QThread>
#include <QTimer>
#include <QDateTime>
typedef struct MYSTRUCT
{
double testData1;
int testData2;
}MYSTRUCT;
class Thread1 : public QObject
{
Q_OBJECT
public:
Thread1(QObject *parent = nullptr);
signals:
void workSignal();
public slots:
void test1Slot(MYSTRUCT *data);
void thread1And2TestSlot();
private slots:
void timerTimeoutSlot();
private:
QThread *m_pThread;
QTimer *m_pTimer;
};
#endif // THREAD1_H
------------------------------------------------------------------
// thread1.cpp
#include "thread1.h"
Thread1::Thread1(QObject *parent) : QObject(parent)
{
m_pThread = new QThread();
this->moveToThread(m_pThread);
m_pTimer = new QTimer();
m_pTimer->setTimerType(Qt::PreciseTimer);
connect(m_pTimer, &QTimer::timeout, this, &Thread1::timerTimeoutSlot);
m_pTimer->moveToThread(m_pThread);
m_pThread->start();
}
void Thread1::test1Slot(MYSTRUCT *data)
{
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")<<__FUNCTION__<<QThread::currentThread();
qDebug()<<"data->testData1: "<<data->testData1<<" ; data->testData2: "<<data->testData2;
m_pTimer->start(1000);
}
void Thread1::timerTimeoutSlot()
{
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")<<__FUNCTION__<<QThread::currentThread();
emit workSignal();
}
void Thread1::thread1And2TestSlot()
{
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")<<__FUNCTION__<<QThread::currentThread();
}
------------------------------------------------------------------
------------------------------------------------------------------
// thread2.h
#ifndef THREAD2_H
#define THREAD2_H
#include <QDebug>
#include <QObject>
#include <QThread>
#include <QDateTime>
class Thread2 : public QObject
{
Q_OBJECT
public:
Thread2(QObject *parent = nullptr);
signals:
void thread1And2TestSignal();
public slots:
void test2Slot();
private:
QThread *m_pThread;
};
#endif // THREAD2_H
------------------------------------------------------------------
// thread2.cpp
#include "thread2.h"
Thread2::Thread2(QObject *parent)
: QObject(parent)
{
m_pThread = new QThread();
this->moveToThread(m_pThread);
m_pThread->start();
}
void Thread2::test2Slot()
{
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")<<__FUNCTION__<<QThread::currentThread();
emit thread1And2TestSignal();
}
------------------------------------------------------------------
------------------------------------------------------------------
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QDebug>
#include <QMainWindow>
#include <QDateTime>
#include <QEventLoop>
#include "thread1.h"
#include "thread2.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
void test1Signal(MYSTRUCT *data);
void test2Signal();
public slots:
void workSlot();
void testCompleteSlot();
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
private:
Ui::MainWindow *ui;
Thread1 *m_pThread1;
Thread2 *m_pThread2;
};
#endif // MAINWINDOW_H
------------------------------------------------------------------
//mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
qDebug()<<__FUNCTION__<<QThread::currentThread();
m_pThread1 = new Thread1();
qRegisterMetaType<MYSTRUCT>("MYSTRUCT");
connect(this, &MainWindow::test1Signal, m_pThread1, &Thread1::test1Slot);
connect(m_pThread1, &Thread1::workSignal, this, &MainWindow::workSlot);
m_pThread2 = new Thread2();
connect(this, &MainWindow::test2Signal, m_pThread2, &Thread2::test2Slot);
connect(m_pThread2, &Thread2::thread1And2TestSignal, m_pThread1, &Thread1::thread1And2TestSlot);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")<<__FUNCTION__<<QThread::currentThread();
MYSTRUCT *pMyStruct = new MYSTRUCT;
pMyStruct->testData1 = 3.14159;
pMyStruct->testData2 = 100;
emit test1Signal(pMyStruct);
}
void MainWindow::workSlot()
{
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")<<__FUNCTION__<<QThread::currentThread();
}
void MainWindow::on_pushButton_2_clicked()
{
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")<<__FUNCTION__<<QThread::currentThread();
emit test2Signal();
}
void MainWindow::testCompleteSlot()
{
qDebug()<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")<<__FUNCTION__<<QThread::currentThread();
}
------------------------------------------------------------------
代码详解
1.上述代码使用 **movetothread()**方式运行;
2.创建两个线程类 Thread1 和 Thread2,Thread1 用于展示主线程与次线程间的通讯,Thread2 用于展示线程间的通讯;
3.点击按钮1,执行按钮1槽函数 “on_pushButton_clicked()”,创建数据结构体实例,并填入具体数据,通过信号传递到 Thread1 中,Thread1 中对应槽函数输出具体数据,并启动定时器,定时向主线程发送信号,测试打印信息如下;
MainWindow QThread(0x1869600)
"2024-08-27 11:11:52" on_pushButton_clicked QThread(0x1869600)
"2024-08-27 11:11:52" test1Slot QThread(0x3359cc8)
data->testData1: 3.14159 ; data->testData2: 100
"2024-08-27 11:11:53" timerTimeoutSlot QThread(0x3359cc8)
"2024-08-27 11:11:53" workSlot QThread(0x1869600)
"2024-08-27 11:11:54" timerTimeoutSlot QThread(0x3359cc8)
"2024-08-27 11:11:54" workSlot QThread(0x1869600)
4.点击按钮2,执行按钮2槽函数 "on_pushButton_2_clicked()",发送信号到 Thread2 ,Thread2执行对应槽函数 "test2Slot()",并发送信号 "thread1And2TestSignal()",Thread1 接收到信号,执行对应槽函数 "thread1And2TestSlot()",测试打印信息如下;
MainWindow QThread(0x19b9600)
"2024-08-27 11:22:52" on_pushButton_2_clicked QThread(0x19b9600)
"2024-08-27 11:22:52" test2Slot QThread(0x33e9b38)
"2024-08-27 11:22:52" thread1And2TestSlot QThread(0x33e9c18)
注意
1.在mainwindow.cpp构造函数中,连接 MainWindow 与 Thread1 间的信号槽前,需通过 qRegisterMetaType(“MYSTRUCT”) 注册自定义数据类型,这样才能通过信号槽传递该数据类型的变量;
2.Thread1 类中的定时器,需要在 MainWindow 中,通过信号槽,来间接启动;
3.注意线程间通过信号槽传递变量时,变量的生命周期。
*还可以使用 **run()*方式运行(不推荐,使用不方便,局限性大);
connect函数的第五个参数:
(1) Qt::AutoConnection
如果发射信号的线程和执行槽函数的线程是同一个线程,此时等同于Qt::DirectConnection;如果不在同一个线程,就等同于Qt::QueuedConnection,是connect函数的默认参数;
(2) Qt::DirectConnection
发射信号和执行槽函数由同一个线程(信号发射的线程)完成,信号发射后槽函数立马执行,执行完毕后才继续执行“emit信号”后面的代码,即“emit信号”是阻塞的;
(3) Qt::QueuedConnection
发射信号的线程和执行槽函数的线程不是在同一个线程,此时发射信号的线程不阻塞,马上返回,当执行槽函数的线程被CPU调度时,就会执行槽函数;
(4) Qt::BlockingQueuedConnection
和Qt::QueuedConnection基本一样,只是发射信号的线程会被阻塞,直到槽函数被执行完毕,如果使用这个属性,需确保发射信号的线程与执行槽函数的线程不同,否则将发生死锁;
(5) Qt::UniqueConnection
唯一关联,该类型可以和上面的类型通过“|”符号配合使用,同一个信号与同一个槽只能调用connect一次,不能多次调用;
Qt进程间数据通讯
方法一 共享内存
原理:两个个进程共用同一片物理内存
实现方式如下:
进程A(数据写入)
//processA.h
#include <QSharedMemory>
class ProcessA
{
...
public:
ProcessA();
~ProcessA();
void writeButtonClicked();
private:
QSharedMemory *m_shareMemory;
...
}
...
---------------------------------------------------------
//processA.cpp
...
ProcessA::ProcessA()
{
...
m_shareMemory = new QSharedMemory(); //实例化QSharedMemory类
m_shareMemory->setKey("TestKey"); //通过setKey()设置标签名称;
//判断当前实例化对象m_shareMemory是否已经与进程连接,如果已经连接,使用函数detach()将与进程分离。
if(m_shareMemory->isAttached())
{
m_shareMemory->detach();
}
//使用函数create()创建共享内存段,判断是否创建成功,若失败,打印错误信息并返回
if(!m_shareMemory->create(50))
{
qDebug() << m_shareMemory->errorString();
return ;
}
...
}
ProcessA::~ProcessA()
{
...
//注意用完以后退出程序时在析构函数中释放
m_shareMemory->detach();
delete m_shareMemory;
m_shareMemory = nullptr;
...
}
void ProcessA::writeButtonClicked()
{
char testStr[50] = "This is my share memory!";
m_shareMemory->lock(); //使用共享内存前需调用lock()将共享内存上锁
char *destination = reinterpret_cast<char *>(m_shareMemory->data());
const char *source = testStr;
memcpy(destination, source, 50); //将数据写入共享内存
m_shareMemory->unlock(); //调用unlock()函数解锁
}
...
进程B(读取写入)
//processB.h
#include <QSharedMemory>
class ProcessB
{
...
public:
ProcessB();
~ProcessB();
void readButtonClicked();
private:
QSharedMemory *m_shareMemory;
...
}
...
---------------------------------------------------------
//processB.cpp
...
ProcessB::ProcessB()
{
...
m_shareMemory = new QSharedMemory(); //实例化QSharedMemory类
m_shareMemory->setKey("TestKey"); //通过函数setKey()设置标签名称;
//连接共享内存与进程
if(!m_shareMemory->attach())
{
qDebug() << "attach m_shareMemory error!";
return ;
}
...
}
ProcessB::~ProcessB()
{
...
m_shareMemory->detach();
delete m_shareMemory;
m_shareMemory = nullptr;
...
}
void ProcessB::readButtonClicked()
{
char testStr[50];
m_shareMemory->lock(); //使用共享内存前需调用lock()将共享内存上锁
const char *source = (char*)m_shareMemory->constData();
char *destination = testStr;
memcpy(destination, source, 50); //从共享内存里读取数据
m_shareMemory->unlock(); //调用unlock()函数解锁
}
...
方法二 socket通信
在两个进程之间创建socket,自己定义一套通信协议,通过socket,实现两个进程间的数据传输,在此不做赘述,可以参考socket通信相关内容。