Qt多线程通信

前言

1 Qt线程间共享数据主要有两种方式:

1)使用共享内存。即使用一个两个线程都能够共享的变量(如全局变量),这样两个线程都能够访问和修改该变量,从而达到共享数据的目的。

2)使用singal/slot机制,把数据从一个线程传递到另外一个线程。

第一种方法在各个编程语言都普遍使用,而第二种方法是QT的特有的,本文主要介绍第二种。

 2 槽参数类型

1) 在线程间使用信号槽进行通信时,槽参数必须使用元数据类型的参数;

2) Qt内生的元数据类型,如int double QString等;

3) 如果要用自定义的数据类型,需要在connect之前将其注册(qRegisterMetaType)为元数据类型。

4) 线程间用“信号与槽”传递引用参数的话,要加const,因为const文字常量存在常量区中,生命周期与程序一样的长。这样可以避免slot调用的时候参数的运行期已过而使引用无效。

connect(m_thread, SIGNAL(MsgSignal(const QString&)),
              this, SLOT(OnMsgSignal(const QString&)));

3 Q_DECLARE_METATYPE与qRegisterMetaType  

(1)Q_DECLARE_METATYPE

如果要使自定义类型或其他非QMetaType内置类型在QVaiant中使用,必须使用该宏。

该类型必须有公有的 构造、析构、复制构造函数。

(2)qRegisterMetaType 

必须使用该函数的两种情况:

如果非QMetaType内置类型要在Qt的属性系统中使用。

如果非QMetaType内置类型要在queued 信号与槽中使用。

两者的关系:

Q_DECLARE_METATYPE展开后是一个特化后的类QMetaTypeId<TYPE>

qRegisterMetaType将某类型注册到MetaType系统中。

QMetaTypeId<TYPE>的类中成员包含对qRegisterMetaType的调用。

一、传递int参数(主线程与子线程)

testthread.h 文件

#ifndef TESTTHREAD_H
#define TESTTHREAD_H
#include <QThread>
#include "msg.h"

class TestThread : public QThread
{
   Q_OBJECT
public:
   explicit TestThread(QObject *parent = 0);

protected:
   void run();

signals:
   void TestSignal(int);

private:
   Msg msg;
};

#endif // TESTTHREAD_H

testthread.cpp文件 

#include "testthread.h"  
  
TestThread::TestThread(QObject *parent) :  
    QThread(parent)  
{  
}  
  
void TestThread::run()  
{  
    //触发信号  
    emit TestSignal(123);  
} 

自定义的类继承了QThread类,重写run函数,然后触发TestSignal信号。

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "testthread.h"

namespace Ui {

   class MainWindow;

}

class MainWindow : public QMainWindow
{
   Q_OBJECT

public:
   explicit MainWindow(QWidget *parent = 0);
   ~MainWindow();

private slots:
   void DisplayMsg(int);

private:
   Ui::MainWindow *ui;
   TestThread *t;

};

#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);

   //进行connect前必须实例化
   t = new TestThread();

   connect(t, SIGNAL(TestSignal(int)), this, SLOT(DisplayMsg(int)));

   //执行子线程
   t->start();
}

void MainWindow::DisplayMsg(int a)
{
   ui->textBrowser->append(QString::number(a));
}

MainWindow::~MainWindow()
{
   delete ui;
} 

Mainwindow里面连接信号槽,并且将收到的int参数显示在界面上。
运行效果:

 

2、传递自定义参数(主线程与子线程)

对以上程序进行简单的修改,使它传递自定义消息。

testthread.h 文件

#ifndef TESTTHREAD_H
#define TESTTHREAD_H

#include <QThread>
#include "msg.h"

class TestThread : public QThread
{
   Q_OBJECT
public:
   explicit TestThread(QObject *parent = 0);
   Msg msg;

protected:
   void run();

signals:
   void TestSignal(Msg); //自定义消息Msg!!!
};

#endif // TESTTHREAD_H

testthread.cpp文件 

#include "testthread.h"

TestThread::TestThread(QObject *parent) :
QThread(parent)
{

}

void TestThread::run()
{
   msg.int_info = 999;
   msg.str_info = "Hello Main Thread!";
   //触发信号
   emit TestSignal(msg);
}

mainwindow.h

 

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include "testthread.h"
#include "msg.h"

namespace Ui {
   class MainWindow;
}

class MainWindow : public QMainWindow
{
   Q_OBJECT

public:
   explicit MainWindow(QWidget *parent = 0);
   ~MainWindow();

private slots:
   void DisplayMsg(Msg); //Msg!!!

private:
   Ui::MainWindow *ui;
   TestThread *t;

};

#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);

   //进行connect前必须实例化
   t = new TestThread();

   //Msg!!!
   connect(t, SIGNAL(TestSignal(Msg)), this, SLOT(DisplayMsg(Msg)));

   //执行子线程
   t->start();
}

void MainWindow::DisplayMsg(Msg msg)
{
   ui->textBrowser->append(QString::number(msg.int_info));
   ui->textBrowser->append(msg.str_info);
}

MainWindow::~MainWindow()
{
   delete ui;
}

此时再进行编译,编译通过,但Qt Creator会有提示: 

QObject::connect: Cannot queue arguments of type 'Msg'
(Make sure 'Msg' is registered using qRegisterMetaType().)

并且运行程序时会发现,信号发送了,槽函数始终不调用。

 

如果将槽参数注册为元数据类型,即mainwindow.cpp文件改动一下:

 

ui->setupUi(this);

qRegisterMetaType<Msg>("Msg");


此时便可正常运行:

 

 ​​​​

 

3、传递自定义参数(子线程与子线程)

 

 

原理同上,然后把connect函数中的第三参数this(主线程)改成要监听的另一个线程对象就好了(QT多么健壮、友好、强大)。

 

connect(t, SIGNAL(TestSignal(Msg)), this, SLOT(DisplayMsg(Msg)));  

前提是全部的线程都要在主线程里面实例化(new)。

4、传递自定义结构体参数(子线程与子线程)

 

实现子线程与GUI子线程的参数进行传递。

线程

头文件 ABFThread.h

public:

struct G_ABFTableSrcUnit
{
   int a;
   int b;
   int c;
   float d;
   float e;
   unsigned int f;
   float Gg;

   QString waveformTypel;
};

public slots:

   void parameterPassing(abfThread::G_ABFTableSrcUnit); //线程自己调用自己的结构体。。。必须这么写不然主线程会报错的 错误是参数内容不一样


ABFThread.cpp

void abfThread::parameterPassing(abfThread::G_ABFTableSrcUnit)
{

}

GUI线程 

radarControl.h

#include "abfThread"  
  
private:  
    Ui::radarControl *ui;  
  
    abfThread::G_ABFTableSrcUnit mst_abfSrcUnit;  
  
  
signals:  
    void sendString(abfThread::G_ABFTableSrcUnit);

radarControl.cpp
按下按钮就发射信号

void radarControl::on_pushButton_clicked()
{
   emit sendString(mst_abfSrcUnit);
}

 

mainwindow.h
#include "abfThread.h"

#include "radarControl.h"

mainwindow.cpp

radarInterface = new radarControl();
m_ABFThread = new QThread();
m_ABF = new abfThread();
m_ABF->moveToThread(m_ABFThread);
m_ABFThread->start();

qRegisterMetaType<abfThread::G_ABFTableSrcUnit>("abfThread::G_ABFTableSrcUnit");
connect(radarInterface,SIGNAL(sendString(abfThread::G_ABFTableSrcUnit)),m_ABF,SLOT(parameterPassing(abfThread::G_ABFTableSrcUnit)));
//除了注册结构体外 还要保证传递的参数写法要一样 这就是为什么 前面线程自己定义的结构体自己调用自己的原因了

 

小结:

 

1 > Qt的信号槽函数只默认支持Qt的类型和C++提供的内建的基本类型,比如int double float等,根本不支持C++的std::string std::vector 自定义的struct类型。所以需要用Qt提供的Q_DECLARE_METATYPE和qRegisterMetaType来声明和注册自定义的类型和C++的其他类型。
2 > 多线程间的信号槽传递,在connect的时候需要以Qt::QueuedConnection的方式,不然以Qt::DirectConnection的方式接收者UI线程会很长时间收不到后台线程发出的信号,或者信号直接丢失都是有可能的

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt多线程通信有多种方式,以下是其中的几种: 1. 信号与槽:在主线程中定义一个信号,然后在子线程中使用槽函数处理信号。这种方式可以实现线程之间的安全通信,避免了共享变量带来的问题。示例代码如下: ``` // 在主线程中定义信号 class MyThread : public QThread { Q_OBJECT public: MyThread(QObject *parent = nullptr) : QThread(parent) {} signals: void mySignal(int value); protected: void run() override { // 子线程中使用槽函数处理信号 QObject::connect(this, &MyThread::mySignal, this, [this](int value) { qDebug() << "Received signal in thread" << QThread::currentThreadId() << "with value" << value; }); exec(); } }; // 在主线程中发送信号 int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); MyThread thread; thread.start(); // 发送信号 QObject::connect(&thread, &MyThread::started, [] { qDebug() << "Sending signal in thread" << QThread::currentThreadId(); emit mySignal(42); }); return a.exec(); } ``` 2. 使用信号槽队列:在子线程中定义一个QObject对象,然后将其移动到主线程中。这样,子线程可以通过调用该对象的信号槽函数来与主线程进行通信。示例代码如下: ``` // 在子线程中定义QObject对象 class Worker : public QObject { Q_OBJECT public: Worker(QObject *parent = nullptr) : QObject(parent) {} signals: void mySignal(int value); public slots: void doWork() { // 子线程中发送信号 emit mySignal(42); } }; // 在主线程中移动QObject对象到主线程中 int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QThread thread; Worker worker; worker.moveToThread(&thread); // 在主线程中连接信号槽 QObject::connect(&thread, &QThread::started, &worker, &Worker::doWork); QObject::connect(&worker, &Worker::mySignal, [] (int value) { qDebug() << "Received signal in thread" << QThread::currentThreadId() << "with value" << value; }); thread.start(); return a.exec(); } ``` 3. QMetaObject::invokeMethod:可以在主线程中使用该方法来调用子线程中的槽函数。示例代码如下: ``` // 在子线程中定义槽函数 class Worker : public QObject { Q_OBJECT public: Worker(QObject *parent = nullptr) : QObject(parent) {} public slots: void doWork(int value) { qDebug() << "Received value" << value << "in thread" << QThread::currentThreadId(); } }; // 在主线程中调用槽函数 int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QThread thread; Worker worker; worker.moveToThread(&thread); thread.start(); // 在主线程中调用槽函数 QMetaObject::invokeMethod(&worker, "doWork", Qt::QueuedConnection, Q_ARG(int, 42)); return a.exec(); } ``` 以上是Qt多线程通信的几种方式,根据具体需求选择合适的方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值