c++ - 如何从 Qt 中的大文件异步加载数据?

标签 c++ multithreading qt io

我正在使用 Qt 5.2.1 来实现一个从文件(可能是几个字节到几 GB)中读取数据并以依赖于每个字节的方式可视化该数据的程序。我这里的示例是一个十六进制查看器。

一个对象进行读取,并在读取新数据 block 时发出信号 dataRead()。信号携带一个指向 QByteArray 的指针,如下所示:

文件阅读器.cpp

void FileReader::startReading()
{

    /* Object state code here... */

        {
            QFile inFile(fileName);

            if (!inFile.open(QIODevice::ReadOnly))
            {
                changeState(STARTED, State(ERROR, QString()));
                return;
            }

            while(!inFile.atEnd())
            {
                QByteArray *qa = new QByteArray(inFile.read(DATA_SIZE));
                qDebug() << "emitting dataRead()";
                emit dataRead(qa);
            }
        }

    /* Emit EOF signal */

}

查看器将其 loadData 插槽连接到此信号,这是显示数据的函数:

十六进制查看器.cpp

void HexViewer::loadData(QByteArray *data)
{
    QString hexString = data->toHex();

    for (int i = 0; i < hexString.length(); i+=2)
    {
        _ui->hexTextView->insertPlainText(hexString.at(i));
        _ui->hexTextView->insertPlainText(hexString.at(i+1));
        _ui->hexTextView->insertPlainText(" ");
    }

    delete data;
}

第一个问题是,如果按原样运行,GUI 线程将变得完全无响应。所有 dataRead() 信号都将在 GUI 重绘之前发出。

(full code 可以运行,当你使用大于 1kB 左右的文件时,你会看到这种行为。)

根据对我的论坛帖子的回复 Non-blocking local file IO in Qt5以及另一个 Stack Overflow 问题的答案 How to do async file io in qt? ,答案是:使用线程。但是,这些答案都没有详细说明如何对数据本身进行洗牌,也没有详细说明如何避免常见错误和陷阱。

如果数据很小(大约一百个字节),我会用信号发送它。但在文件大小为 GB 的情况下(编辑)或者如果文件位于基于网络的文件系统上,例如。 NFS、Samba 共享,我不希望 UI 仅仅因为读取文件 block 而锁定。

第二个问题是,在发射器中使用 new 和在接收器中使用 delete 的机制似乎有点天真:我有效地使用整个堆作为跨线程队列。

问题 1:Qt 是否有更好/惯用的方法来跨线程移动数据同时限制内存消耗?它有线程安全队列或其他可以简化整个事情的结构吗?

问题 2:自己实现线程等吗?我不太喜欢重新发明轮子,尤其是在内存管理和线程方面。是否有更高级别的构造已经可以做到这一点,比如网络传输?

最佳答案

首先,您的应用中根本没有任何多线程。你的FileReader类是QThread的子类,但并不意味着所有的FileReader方法都会在另一个线程中执行。事实上,您的所有操作都在主 (GUI) 线程中执行。

FileReader 应该是 QObject 而不是 QThread 子类。然后,您创建一个基本的 QThread 对象,并使用 QObject::moveToThread 将您的工作人员(阅读器)移动到它。您可以阅读有关此技术的信息 here .

确保您已使用 qRegisterMetaType 注册了 FileReader::State 类型。这是 Qt 信号槽连接跨不同线程工作所必需的。

一个例子:

HexViewer::HexViewer(QWidget *parent) :
    QMainWindow(parent),
    _ui(new Ui::HexViewer),
    _fileReader(new FileReader())
{
    qRegisterMetaType<FileReader::State>("FileReader::State");

    QThread *readerThread = new QThread(this);
    readerThread->setObjectName("ReaderThread");
    connect(readerThread, SIGNAL(finished()),
            _fileReader, SLOT(deleteLater()));
    _fileReader->moveToThread(readerThread);
    readerThread->start();

    _ui->setupUi(this);

    ...
}

void HexViewer::on_quitButton_clicked()
{
    _fileReader->thread()->quit();
    _fileReader->thread()->wait();

    qApp->quit();
}

这里也没有必要在堆上分配数据:

while(!inFile.atEnd())
{
    QByteArray *qa = new QByteArray(inFile.read(DATA_SIZE));
    qDebug() << "emitting dataRead()";
    emit dataRead(qa);
}

QByteArray 使用 implicit sharing .这意味着当您以只读模式跨函数传递 QByteArray 对象时,其内容不会被一次又一次地复制。

将上面的代码改成这样,忘记手动内存管理:

while(!inFile.atEnd())
{
    QByteArray qa = inFile.read(DATA_SIZE);
    qDebug() << "emitting dataRead()";
    emit dataRead(qa);
}

但无论如何,主要问题不在于多线程。问题是 QTextEdit::insertPlainText 操作并不便宜,尤其是当您有大量数据时。 FileReader 非常快速地读取文件数据,然后用要显示的新数据部分淹没您的小部件。

必须注意的是,您有一个非常无效的 HexViewer::loadData 实现。您逐个字符地插入文本数据,这使得 QTextEdit 不断重绘其内容并卡住 GUI。

您应该首先准备生成的十六进制字符串(注意数据参数不再是指针):

void HexViewer::loadData(QByteArray data)
{
    QString tmp = data.toHex();

    QString hexString;
    hexString.reserve(tmp.size() * 1.5);

    const int hexLen = 2;

    for (int i = 0; i < tmp.size(); i += hexLen)
    {
        hexString.append(tmp.mid(i, hexLen) + " ");
    }

    _ui->hexTextView->insertPlainText(hexString);
}

无论如何,您的应用程序的瓶颈不是文件读取而是 QTextEdit 更新。按 block 加载数据,然后使用 QTextEdit::insertPlainText 将其附加到小部件不会加快任何速度。对于小于 1Mb 的文件,一次读取整个文件然后一步将生成的文本设置到小部件会更快。

我想您不能使用默认的 Qt 小部件轻松显示大于几兆字节的大文本。此任务需要一些重要的方法,通常与多线程或异步数据加载无关。这一切都是关于创建一些不会尝试立即显示其大量内容的棘手小部件。

关于c++ - 如何从 Qt 中的大文件异步加载数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34572043/

相关文章:

c++ - 如何在 C++ 中创建这个 yml 文件(使用 opencv)

c++ - 为什么我的 C++ 代码比 C 代码慢得多

c++ - 如何计算 C++ 中一个函数的 CPU 消耗

ruby-on-rails - Ruby on Rails 能否利用英特尔的多核处理能力?

python - 图表 (pyqtgraph) 未在 QML 生成的窗口中绘制

android - QML 屏幕方向锁定

c++ - tile_static 动态索引数组;我还应该打扰吗?

r - R中的多线程数据表比使用单线程的慢得多

c++ - Boost WITH Writer block 中的多读取器、单写入器锁定

c++ - 如何区分 QNetworkReply 是否中止?