Qt多线程问题2

现在有这么一个场景,有多个数据发送线程发送数据,我们想要将这些数据都记录在一个文件中,考虑了如下这样一种设计:先实现一个数据发送线程,用向量管理起来。

我们在写文件的时候就是要写磁盘,那么I/O速率一般是慢于内存操作速率的,所以可以考虑使用双缓冲的机制来缓存文件,设计如下:实现一个Manager管理类,里边包含两个线程,一个线程负责接收发送线程的数据存入内存,另一个线程等到缓冲区满时直接写数据。代码结构如下:

数据发送线程DataSendThread实现如下:

#ifndef DATASENDTHREAD_H
#define DATASENDTHREAD_H

#include <QVector>
#include <QThread>
#include <QDebug>
#include <QElapsedTimer>

class DataSendThread: public QThread
{
    Q_OBJECT
public:
    explicit DataSendThread(QObject* parent = nullptr, int start = 0);

protected:
    void run() override;

private:
    QVector<int>     _data;
    int              _start;

signals:
    void emitSendData(int val);
};

#endif // DATASENDTHREAD_H

其中,_data是我们要发送的数据,_start负责初始化起始的发送数据。成员函数实现如下:

#include "DataSendThread.h"

DataSendThread::DataSendThread(QObject* parent, int start):
    _start(start)
{
    /* 初始化要发送的数据 */
    for(int i = start; i < start + 20; i++)
        _data.push_back(i);
}

void DataSendThread::run()
{
    for(auto& data: _data){
        QElapsedTimer t; t.restart();
        while(t.elapsed() < 50);
        emit emitSendData(data);
    }
}

通过Qt的emit信号发送数据,发送间隔为50ms. 实现一个线程管理类DataReceiveManager,实现如下:

#ifndef DATARECEIVEMANAGER_H
#define DATARECEIVEMANAGER_H

#include <QMutex>
#include <QMutexLocker>
#include <QObject>
#include <QThread>
#include <QVector>
#include <QSharedPointer>
#include <QQueue>
#include <QScopedPointer>
#include "DataReceiveThread.h"
#include "DataWriteThread.h"

class DataReceiveManager: public QObject
{
    Q_OBJECT
public:
    explicit DataReceiveManager(QObject* parent = nullptr);

    void checkResult();

public slots:
    void saveData(int i);

private:
    QSharedPointer<QVector<int>>            _buffer_1;              // 缓冲1
    QSharedPointer<QVector<int>>            _buffer_2;              // 缓存2

    QSharedPointer<QVector<int>>            _result_vector;         // 汇总所有的结果,最后用来打印验证

    QScopedPointer<DataReceiveThread>       _receive_thread;        // 数据接收线程
    QScopedPointer<DataWriteThread>         _data_write_thread;     // 数据写文件线程

    int                                     _cache_size;
    QMutex                                  _mutex;                 // 加锁
};

#endif // DATARECEIVEMANAGER_H

首先使用智能指针管理两块缓冲区,用_result_vector记录最终的收数结果;定义一个收数的接口,负责从DataSendThread接收数据;_receive_thread负责接收数据写内存,_data_write_thread负责模拟写磁盘;_cache_size是内存缓冲区大小。

#include "DataReceiveManager.h"

DataReceiveManager::DataReceiveManager(QObject* parent)
    : _buffer_1(new QVector<int>)
    , _buffer_2(new QVector<int>)
    , _result_vector(new QVector<int>)
    , _receive_thread(new DataReceiveThread)
    , _data_write_thread(new DataWriteThread)
    , _cache_size(4)
{
    _receive_thread->resetBuffer(_buffer_1);
}

void DataReceiveManager::checkResult()
{
    QString temp;

    for(int i = 0; i < _result_vector.data()->size(); i++){
        temp += (QString::number(_result_vector->data()[i]) + " ");
    }

    qDebug() << "Curr Result: " << temp;
    qDebug() << "Manager: total receive " << _result_vector->size();
    qDebug() << "Buffer_1 Size: " << QString::number(_buffer_1->size())
             << "| Buffer_2 Size: " << QString::number(_buffer_2->size());
}

void DataReceiveManager::saveData(int i)
{
    QMutexLocker lock(&_mutex);

    if(_buffer_1->size() > _cache_size){
        /* 通过智能指针快速交换数据地址 */
        _buffer_2.data()->clear();
        _buffer_1.swap(_buffer_2);
        _receive_thread->resetBuffer(_buffer_1);

        /* 缓冲区满,使用一个线程模拟磁盘操作 */
        _data_write_thread->saveDataToDisk(_buffer_2, _result_vector);
    }

    /* 使用一个线程写入缓冲区 */
    _receive_thread->saveData(i);
    this->checkResult();
}

因为有多个线程会同时访问saveData接口,那么就需要加锁,加锁后,判断缓冲区有没有满,满了通过智能指针swap后,写内存的线程继续在空的缓存位置写数据,满的缓冲区交给另一个线程保存。

现在实现数据写内存线程DataReceiveThread:

#ifndef DATARECEIVETHREAD_H
#define DATARECEIVETHREAD_H

#include <QVector>
#include <QThread>
#include <QQueue>
#include <QMutex>
#include <QDebug>
#include <QSharedPointer>
#include <QElapsedTimer>

class DataReceiveThread: public QThread
{
    Q_OBJECT
public:
    DataReceiveThread();

    void resetBuffer(QSharedPointer<QVector<int>>& buffer);

    void saveData(int i);

protected:
    void run() override;

private:
    QSharedPointer<QVector<int>>       _buffer;
    QSharedPointer<QQueue<int>>        _queue;

    QQueue<int>     _receive_queue;
    QMutex          _mutex;
};

#endif // DATARECEIVETHREAD_H

成员变量和缓冲区是绑定的,用智能指针来进行设置赋值操作比直接复制原数据节省时间。

#include "DataReceiveThread.h"

DataReceiveThread::DataReceiveThread()
{

}

void DataReceiveThread::resetBuffer(QSharedPointer<QVector<int> > &buffer)
{
    _buffer = buffer;
}

void DataReceiveThread::saveData(int i)
{
    QMutexLocker lock(&_mutex);

    if(_receive_queue.size() < 20){
        _receive_queue.enqueue(i);
    }

    if(!this->isRunning() && !_receive_queue.isEmpty()){
        this->start();
    }
}

void DataReceiveThread::run()
{
    while(!_receive_queue.isEmpty()){
        QMutexLocker lock(&_mutex);
        auto data = _receive_queue.dequeue();
        lock.unlock();

        QElapsedTimer t; t.restart();
        while(t.elapsed() < 8);

        _buffer->append(data);
        qDebug() << "Receive queue size: " << QString::number(_receive_queue.size())
                 << "  Try save an element: " << QString::number(data)
                 << "  Curr Buffer Size: " << QString::number(_buffer->size());
    }
}

DataReceiveThread接收数据时都是通过接口saveData进行的,写数据到队列后直接启动线程。在run()函数中,模拟内存的耗时操作为8ms. 

实现写磁盘线程DataWriteThread类:

#ifndef DATAWRITETHREAD_H
#define DATAWRITETHREAD_H

#include <QDebug>
#include <QThread>
#include <QVector>
#include <QElapsedTimer>
#include <QSharedPointer>

class DataWriteThread: public QThread
{
    Q_OBJECT
public:
    DataWriteThread();

    void saveDataToDisk(QSharedPointer<QVector<int>>& data, QSharedPointer<QVector<int>>& result);

protected:
    void run() override;

private:
    QSharedPointer<QVector<int>>    _data;
    QSharedPointer<QVector<int>>    _result;
    QString                         _temp;
    int                             _receive_count;
};

#endif // DATAWRITETHREAD_H

写磁盘都是通过saveDataToDisk来模拟的,具体实现如下:

#include "DataWriteThread.h"

DataWriteThread::DataWriteThread()
    : _receive_count(0)
{

}

void DataWriteThread::saveDataToDisk(QSharedPointer<QVector<int> > &data, QSharedPointer<QVector<int>>& result)
{
    // 设置存储的位置
    _data = data;
    _result = result;

    if(!this->isRunning())
        this->start();
}

void DataWriteThread::run()
{
    for(int i = 0; i < _data->size(); i++)
        _result->append(_data->data()[i]);

    // 模拟耗时操作
    QElapsedTimer t; t.restart();
    while(t.elapsed() < 25);

    for(int i = 0; i < _data->size(); i++){
        _temp += (QString::number(_data->data()[i]) + " ");
        _receive_count++;
    }

    qDebug() << "Save Data to Disk. Tolal Receive: " <<QString::number(_receive_count) << " Curr Result: " << _temp;
}

在run()函数中,模拟耗时操作为25ms.

现在再实现一个Handler来模拟一个发送数据接收数据的过程,实现如下:

#ifndef HANDLER_H
#define HANDLER_H

#include <QObject>
#include "DataReceiveManager.h"
#include "DataSendThread.h"

class Handler: public QObject
{
    Q_OBJECT
public:
    Handler();

    ~Handler();

    void start();

    DataReceiveManager*         _manager;
    QVector<DataSendThread*>    data_send_threads;
};

#endif // HANDLER_H

成员函数实现如下:

#include "Handler.h"

Handler::Handler()
    : _manager(new DataReceiveManager(this))
{
    QVector<int> init_data{1,21,41,61,81};

    for(auto& data: init_data){
        DataSendThread* _thread = new DataSendThread(this, data);

        /* 初始化所有的信号连接 */
        connect(_thread, &DataSendThread::emitSendData,
                _manager, &DataReceiveManager::saveData);

        data_send_threads.append(_thread);
    }
}

Handler::~Handler()
{
    if(_manager)
        delete _manager;
}

void Handler::start()
{
    qDebug() << "Start All DataSendThreads.";
    for(auto th: data_send_threads)
        th->start();
}

其中,init_data负责模拟发送数据的链路数,加入我们有5路数据需要接收;在堆上分配5个数据发送线程,同时绑定到管理类的数据接收接口;通过start()函数启动所有发送线程。主函数调用如下:

#include <QCoreApplication>
#include <QDebug>
#include <QObject>
#include "Handler.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Handler hander;
    hander.start();

    qDebug() << "Finished!";
    return a.exec();
}

执行主函数结果如下:

观察最后的执行结果:Manager: totol receive 90,说明已经收到了90个数据,剩下的10个观察还没写入的缓存区,在最后一行:Curr Buffer Size: 10,总和正好是我们发送的100个数据,说明没有丢数。

注意事项

1. 数据接收线程对数据的处理速度一定要快,加入你现在同时监听5路数据,每一路的发送频率是50ms一包,那么这些线程抢占锁导致互斥,写内存的速率尽量要再 50ms / 5 = 10 ms 上下,否则会丢数据。

2. 写磁盘的速率一定要快于缓冲区填满的速率,不然也会丢数据。

3. 关于信号与槽:我在实现connect函数时,发现线程的信号不能触发DataReceiveManager管理类的saveData函数,在第一次实现主函数时,我是这样写的:

#include <QCoreApplication>
#include <QDebug>
#include <QObject>
#include "Handler.h"

int main(int argc, char *argv[])
{
    // QCoreApplication a(argc, argv);
    Handler hander;
    hander.start();

    qDebug() << "Finished!";
    // return a.exec();
    return 0;
}

相当于没有执行主函数的事件循环,那么通过队列连接的信号就无法在主函数触发相应的槽,修正后就可以监听到了;当然也可以用直接连接的方式DirectConnection,就不用进队列了,不过有风险就是了。最后总结一下,锁互斥是很占用时间和资源的,如果可以尽量还是分开记录数据比较好一点。

以上就是平时的一些学习与实践,欢迎多多交流~

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt线程是Qt框架提供的一种多线程编程的解决方案,通过创建和管理多个线程,可以实现在程序中同时执行多个任务的能力。Qt提供了QThread类和QtConcurrent命名空间来支持多线程。 QThread类是Qt提供的多线程编程的基础类,它封装了与底层操作系统调用相关的函数,并提供了一套简单的API来管理线程的生命周期。通过继承QThread类,我们可以创建自己的线程类,并实现线程执行体run()函数。调用线程的start()函数即可启动线程,并在run()函数中实现需要在新线程中执行的任务。另外,QThread类还提供了其他一些方法,如wait()、msleep()等,用于线程的控制。 QtConcurrent命名空间是Qt提供的一个高级多线程编程的工具,其使用起来更加简单方便。QtConcurrent可以运行同步和异步的任务,主要通过函数调用和lambda表达式来实现。我们可以使用QtConcurrent提供的函数,比如map()、filter()等,将任务分解成多个小的任务,并在多个线程中同时执行。使用QtConcurrent,我们只需要关注任务的输入和输出,而无需关注线程的创建和管理。 无论是使用QThread类还是QtConcurrent命名空间,Qt线程都遵循的原则是将任务分解成小的子任务,并在多个线程中同时执行,以提高程序的性能和响应速度。同时,Qt线程也提供了线程间的通信机制,例如使用信号与槽机制、使用QMutex、QSemaphore等同步对象来保证数据的正确访问。 需要注意的是,在多线程编程中,我们需要注意线程之间的同步和互斥,以避免资源竞争和数据不一致等问题Qt提供了多种同步和互斥的机制,如互斥锁、条件变量等,可以根据具体的需求选择合适的方式来保证线程之间的安全性。 总之,Qt线程提供了一种方便易用的多线程编程解决方案,使我们可以在Qt程序中充分利用多核处理器的能力,提高程序的性能和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值