首页 > 编程语言 >QT/C++中的GDAL多线程应用(读取):发生的问题以及解决方案

QT/C++中的GDAL多线程应用(读取):发生的问题以及解决方案

时间:2024-08-28 09:52:12浏览次数:7  
标签:QT band C++ 互斥 线程 多线程 256 GDAL

1. 引言

在使用GDAL库对TIF文件进行切割和创建瓦片金字塔时,为了提高创建效率,不得不考虑使用多线程处理。然而,在实际实现过程中,我遇到了许多问题。通过不断的尝试和优化,最终找到了有效的解决方案。本文将详细记录这一过程中的问题和解决方法。

2. 初始多线程尝试与问题

2.1 常规多线程实现

最初,我尝试使用常规的多线程方法来处理TIF文件切割,但很快发现这一方法导致了大量错误:

ERROR1: GF2-035009809.tif,band 2: IReadBlock failed at X offset 0, Y offset 4373,
ERROR1: GetBlockRef failed at X block offset 0, Y block offset 4373,
ERROR1: TIFFFillStrip: Read error at scanline 2; got 0 bytes, expected 18438,
ERROR1: RIFFReadEncodedStrip() failed.

随着线程数量的增加,这些错误发生的次数也随之增加,有时甚至可以超过几千次。
在这里插入图片描述

2.2 问题分析

分析这些错误后,我发现其根本原因在于多线程环境下GDAL库访问资源时可能发生冲突。具体来说,当多个线程同时读取某一位置的影像块时,会引发冲突,因为每个像素在同一时刻只能由一个线程读取。

3. 改进方案:使用互斥锁

3.1 使用互斥锁保护资源

为了解决上述问题,我决定使用互斥锁来保护共享资源,从而确保线程安全。

3.2 核心代码

以下是使用互斥锁的核心代码:

//与多线程无关代码已删除
void run() override
{
    // 获取信号量, 控制并发数,限制同时运行的线程数量
    semaphore->acquire();

    // 使用互斥锁保护共享资源,确保线程安全
    QMutexLocker locker(mutex);

    // 创建GeoTIFF驱动并创建目标数据集
    GDALDriver *driver = GetGDALDriverManager()->GetDriverByName("GTiff");
    GDALDataset *dstDataset = driver->Create(outputFile.toUtf8().constData(), 256, 256, srcDataset->GetRasterCount(), GDT_Byte, NULL);

    // 缓冲区
    std::vector<uint8_t> buffer(256 * 256);
    //srcDataset为源数据集
    // 对每个波段进行处理
    for (int band = 1; band <= srcDataset->GetRasterCount(); ++band) {
        GDALRasterBand *srcBand = srcDataset->GetRasterBand(band);
        GDALRasterBand *dstBand = dstDataset->GetRasterBand(band);

        // 读取源数据并写入目标数据集
        srcBand->RasterIO(GF_Read, srcX, srcY, tileWidth, tileHeight, buffer.data(), 256, 256, GDT_Byte, 0, 0);
        dstBand->RasterIO(GF_Write, 0, 0, 256, 256, buffer.data(), 256, 256, GDT_Byte, 0, 0);
    }
    // 关闭目标数据集
    GDALClose(dstDataset);
    // 解锁互斥锁
    locker.unlock();
    // 释放信号量,允许其他线程继续执行
    semaphore->release();
}

3.3 结果分析

尽管使用互斥锁成功解决了线程冲突问题,但测试发现,处理效率却有所下降。这可能是因为:

  1. 代码的主要耗时操作在I/O操作中。
  2. 使用互斥锁后,实际上仍然是单线程I/O。
  3. 线程切换带来了额外的开销。

通过任务管理器观察,发现进程的CPU、内存以及磁盘的利用率都不高。
同时通过查阅一些资料,发现gdal库好像并不支持多线程读取
在这里插入图片描述

4. 改进方案:独立数据集

4.1 独立数据集实现

由于之前所有线程共用一个数据集,需要使用互斥锁保护资源,导致处理效率不高。因此,我尝试为每个线程创建一个独立的数据集,从而避免使用互斥锁。

4.2 核心代码

以下是为每个线程创建独立数据集的核心代码:

void run() override
{
    semaphore->acquire();
    // 为每个线程创建独立的数据集副本
    GDALDataset *srcDataset = (GDALDataset*)GDALOpen(inputFile.toUtf8().constData(), GA_ReadOnly);
    if (!srcDataset) {
        semaphore->release();
        return;
    }
    // 创建GeoTIFF驱动并创建目标数据集
    GDALDriver *driver = GetGDALDriverManager()->GetDriverByName("GTiff");
    GDALDataset *dstDataset = driver->Create(outputFile.toUtf8().constData(), 256, 256, srcDataset->GetRasterCount(), GDT_Byte, NULL);
    std::vector<uint8_t> buffer(256* 256);
    for (int band = 1; band <= srcDataset->GetRasterCount(); ++band) {
        GDALRasterBand *srcBand = srcDataset->GetRasterBand(band);
        GDALRasterBand *dstBand = dstDataset->GetRasterBand(band);
        // 读取源数据并写入目标数据集
        srcBand->RasterIO(GF_Read, srcX, srcY, tileWidth, tileHeight,
                          buffer.data(), blockSize, blockSize, GDT_Byte, 0, 0);

        dstBand->RasterIO(GF_Write, 0, 0, blockSize, blockSize,
                          buffer.data(), blockSize, blockSize, GDT_Byte, 0, 0);
    }

    // 关闭数据集
    GDALClose(dstDataset);
    GDALClose(srcDataset);
    semaphore->reease();
}

4.3 结果分析

虽然给每个线程创建独立的数据集后,不再需要使用互斥锁,也没有再出现大量的读取错误,但处理效率依然没有显著变化。

5. 最终解决方案:GDAL多线程环境变量

通过进一步研究GDAL库的官方文档,我发现GDAL库本身其实是支持多线程读取功能,只需在程序启动时设置相应的环境变量即可。这意味着之前的多线程优化尝试未能成功的原因在于GDAL库默认没有开启多线程支持。

5.1 环境变量设置

通过设置GDAL_NUM_THREADS环境变量,GDAL库可以解锁多线程读取功能。以下是设置环境变量的代码:

// 在注册GDAL驱动【GDALAllRegister()】后马上设置环境变量  
CPLSetConfigOption("GDAL_NUM_THREADS", "16"); // 使用16个线程
CPLSetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS"); // 使用所有CPU核心

5.2 优化效果

经过设置GDAL_NUM_THREADS环境变量后,处理效率得到了显著提升。

6. 结论

在使用GDAL库进行多线程处理时,直接使用常规的多线程方法可能会导致资源冲突和处理效率低下。通过合理使用互斥锁或创建独立数据集可以在一定程度上解决这些问题,但效果有限。

通过设置GDAL库的环境变量来解锁多线程读取功能,我们可以显著提升处理效率。这个经验说明,深入了解第三方库的特性和配置选项是非常重要的,这样可以帮助我们最大限度地发挥其性能。

标签:QT,band,C++,互斥,线程,多线程,256,GDAL
From: https://blog.csdn.net/NCHUkiss/article/details/141428123

相关文章

  • 多线程的使用-->2
    1.线程同步当多线程执行,对于一个共享变量进行操作时,会产生一些安全问题,称为同步问题。线程同步就是在多线程访问共享变量时,通过一定的机制,使得线程可以按照一定的顺序,每次只能有一个线程访问共享变量。1.1解决线程同步给程序枷锁   两种:①.悲观锁:必须要上锁,有具体的......
  • 南沙C++陈老师讲题:1078:求分数序列和
    ​【题目描述】【输入】输入有一行,包含一个正整数n(n≤30)n(n≤30)。【输出】输出有一行,包含一个浮点数,表示分数序列前nn项的和,精确到小数点后44位。【输入样例】2【输出样例】3.5000#include<iostream>#include<stdio.h>usingnamespacestd;intmain()......
  • 多线程四-Lock锁及其原理分析
    JUC是什么可能有些不太关注底层代码,会不太理解juc是啥,比如之前的我,只知道是跟并发相关。juc其实就是并发包路径的缩写,java.util.concurrent.而Lock是其中锁的接口,有比如重入锁,读锁,写锁等一些具体实现。这部分源码理解起来还是有些难度,暂时先理解其大概思路,对于实现有一个印象,比......
  • 【PyQt5 应用程序】PyQt基础组件:按钮
    在任何图形用户界面(GUI)应用程序中,按钮是最基本也是最频繁使用的组件之一。它们是用户与应用程序交互的主要方式之一。在PyQt中,按钮可以通过QPushButton类创建,它提供了丰富的功能,包括显示文本、图像,以及响应点击事件。本节将引导你了解如何在PyQt应用中创建和使用按钮,并通过......
  • 二叉树的层序遍历 C++
    给你二叉树的根节点 root ,返回其节点值的 层序遍历 。(即逐层地,从左到右访问所有节点)。示例1:输入:root=[3,9,20,null,null,15,7]输出:[[3],[9,20],[15,7]]示例2:输入:root=[1]输出:[[1]]示例3:输入:root=[]输出:[]classSolution{public:vector<vect......
  • 根据二叉树创建字符串 C++
    给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。空节点使用一对空括号对 "()" 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。示例1:输入:root=[1,2,3,4]输出:"1......
  • C++智能指针
    1.为什么需要智能指针大家来看下面这段程序我们new了两个arraydoubleDivision(inta,intb){ //当b==0时抛出异常 if(b==0) { throw"Divisionbyzerocondition!"; } return(double)a/(double)b;}voidFunc(){ int*array1=newint[10]; int*......
  • C++面向对象三大特性之一(封装)
    下面这篇文我来给大家分享C++面向对象三大特性之一(封装)。一、什么是封装?分装就是一个类中的私有成员,虽然类外不可以访问,但是我们提供一些公共的接口来间接让其他人访问到,例如一个人的名字我们起好之后就一般不会允许其他人改你的姓名,但是我们可以通过一些方式得到你的姓名......
  • C++/C区别
    C++/C差别typedef和using的差别,typedef可以用来定义一个类型,也可以用于定义别名。using还是做不到定义类型,但是可以用于别名。voidAadd(){/*code*/}voidBadd(){/*code*/}typedefvoid(*PFunc)();//定义一个函数指针类型PFuncfunc=Aadd;PFuncfu......
  • 链表简介c++
    定义:链表是一种数据结构,其中元素(也称为节点)不是连续存储的。每个节点包含数据部分和一个指向下一个节点的指针。类型:在C++中,有两种主要类型的链表:单链表(每个节点只有一个指针指向下一个节点)和双向链表(每个节点有两个指针,一个指向前一个节点,另一个指向后一个节点)。结构......