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 
Q_DECLARE_METATYPE

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

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

qRegisterMetaType 

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

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

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

两者的关系:

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

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

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

1、传递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_METATYPEqRegisterMetaType来声明和注册自定义的类型和C++的其他类型。
2 > 多线程间的信号槽传递,在connect的时候需要以Qt::QueuedConnection的方式,不然以Qt::DirectConnection的方式接收者UI线程会很长时间收不到后台线程发出的信号,或者信号直接丢失都是有可能的


### QT多线程通信的实现方式 在QT框架中,多线程通信可以通过多种机制实现,主要包括`QThread`、`QMutex`、`QWaitCondition`以及`QReadWriteLock`等工具。以下是几种常见的实现方法及其示例。 #### 方法一:使用 `QThread` 和信号槽机制 `QThread` 是 QT 中实现多线程的核心类之一。通过继承 `QThread` 并重新实现其 `run()` 函数,可以启动一个新的线程。利用 QT 的信号槽机制可以在不同线程传递数据[^3]。 ```cpp #include <QThread> #include <QObject> #include <QDebug> class Worker : public QObject { Q_OBJECT public slots: void doWork() { qDebug() << "Working..."; // 模拟耗时操作 QThread::sleep(2); emit resultReady("Finished"); } signals: void resultReady(const QString &result); }; class Controller : public QObject { Q_OBJECT public: Controller(QObject *parent = nullptr) : QObject(parent), worker(new Worker()) {} ~Controller() { delete worker; } public slots: void handleResults(const QString &result) { qDebug() << "Result received:" << result; } private: Worker *worker; public: void startWorkInAThread() { QThread *thread = new QThread; worker->moveToThread(thread); connect(thread, &QThread::started, worker, &Worker::doWork); connect(worker, &Worker::resultReady, this, &Controller::handleResults); connect(worker, &Worker::resultReady, thread, &QThread::quit); connect(worker, &Worker::resultReady, worker, &Worker::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); } }; ``` 上述代码展示了一个简单的控制器和工作线程模型。当主线程调用 `startWorkInAThread()` 后,新线程会执行耗时任务并将结果返回给主线程[^3]。 --- #### 方法二:使用 `QReadWriteLock` 进行线程间资源共享 对于需要在线程间共享的数据,可以使用 `QReadWriteLock` 来保护资源访问的安全性。这种方式特别适合于读多写少的情况[^4]。 ```cpp #include <QReadWriteLock> #include <QReadLocker> #include <QWriteLocker> #include <QString> #include <QDebug> class SharedResource { public: QString readResource() const { QReadLocker locker(&lock); return data; } void writeResource(const QString &value) { QWriteLocker locker(&lock); data = value; } private: mutable QReadWriteLock lock; QString data; }; void readerFunction(SharedResource *resource) { qDebug() << resource->readResource(); } void writerFunction(SharedResource *resource) { resource->writeResource("New Value"); } ``` 在这个例子中,`readerFunction` 可以同时由多个线程调用而不会引发冲突;然而,只有单个线程能够调用 `writerFunction` 修改资源。 --- #### 方法三:使用 `QtConcurrent` 和 `QFuture` `QtConcurrent` 提供了一种更高级别的抽象来简化并发编程。它可以自动管理线程池,并且不需要手动创建或销毁线程对象[^4]。 ```cpp #include <QCoreApplication> #include <QtConcurrent/QtConcurrentRun> #include <QFuture> #include <QDebug> int compute() { qDebug() << "Computing..."; QThread::sleep(2); // 模拟耗时计算 return 42; } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QFuture<int> future = QtConcurrent::run(compute); // 异步运行函数 while (!future.isFinished()) { qDebug() << "Waiting for the computation to finish..."; QThread::msleep(500); } qDebug() << "Result:" << future.result(); return app.exec(); } ``` 此代码片段展示了如何使用 `QtConcurrent::run` 执行异步任务,并通过轮询检查任务状态直到完成[^4]。 --- #### 方法四:基于 UDP Socket 的多线程通信 除了传统的线程间同步外,在某些场景下可能还需要与其他设备或服务进行网络通信。此时可结合 QT多线程能力和网络模块(如 QUdpSocket),构建高效的通信方案[^2]。 ```cpp #include <QUdpSocket> #include <QThread> #include <QDebug> class UdpServer : public QObject { Q_OBJECT public: explicit UdpServer(quint16 port, QObject *parent = nullptr) : QObject(parent), udpSocket(new QUdpSocket(this)) { udpSocket->bind(port); connect(udpSocket, &QUdpSocket::readyRead, this, &UdpServer::processPendingDatagrams); } private slots: void processPendingDatagrams() { while (udpSocket->hasPendingDatagrams()) { QByteArray datagram; datagram.resize(udpSocket->pendingDatagramSize()); udpSocket->readDatagram(datagram.data(), datagram.size()); qDebug() << "Received message:" << QString(datagram); } } private: QUdpSocket *udpSocket; }; // 将 UdpServer 放置在一个独立的工作线程中... ``` 该实例说明了如何设置一个多线程环境下的 UDP 数据接收器[^2]。 --- ### 总结 以上介绍了四种不同的 QT 多线程通信实现方式,每一种都有各自的应用场景和技术特点。开发者可以根据实际需求选择合适的策略来优化程序性能与稳定性。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值