C++代码利用pthread线程池与curl批量下载地图瓦片数据

时间:2023-03-10 04:18:35
C++代码利用pthread线程池与curl批量下载地图瓦片数据

项目需求编写的程序,稳定性有待进一步测试。

适用场景:在网络地图上,比如天地图与谷歌地图,用户用鼠标在地图上拉一个矩形框,希望下载该矩形框内某一层级的瓦片数据,并将所有瓦片拼接成一个完整的,包含地理坐标的tif图像。

之前利用gdal2tiles.py中的代码改写成c++版本,实现了网格瓦片的计算,获得了矩形范围内某一具体层级所包含的所有瓦片的网络请求地址。

见:http://www.cnblogs.com/akaishi/p/7418799.html

那么用c++编写的后端代码如何能快速、大批量下载瓦片图像?

下载使用curl库,地址:https://curl.haxx.se/download.html

加速的办法先是尝试了OpenMP,加速效果有限,优化了几天1千幅瓦片的下载速度还是超过1分钟。

为了进一步提高下载速度,打算从更基础的多线程pthead库编写下载代码。

网上查找了并尝试了几个线程池的代码实现,速度差别还是浮动挺大的,最后采用的一个简单的实现,代码长度短,速度也理想,就是忘记从哪里找的了,这里贴出来吧:

头文件:

#ifndef __THREAD_POOL_H__
#define __THREAD_POOL_H__

#include <stdio.h>
#include <pthread.h>
#include <assert.h>
#include <iostream>
#include <list>

using namespace std;

class CJob{
public:
CJob(void* (*r)(void* arg), void* a) : callback_routine(r), arg(a){}
~CJob(){}

void* (*callback_routine) (void* arg);
void* arg;
};

//fixed size thread pool
class CThreadPool{
public:
CThreadPool(int max_th_num);
~CThreadPool();

int pool_add_job(void* (*process)(void* arg), void* arg);

pthread_mutex_t queue_mutex;
pthread_cond_t queue_cond;
list<CJob*> queue_job;
pthread_t* thread_vec;
int max_thread_num;
int cur_queue_size;
int shutdown;
};

#endif

cpp文件:

#include "thread_pool.h"

static void* thread_routine(void* arg){
CThreadPool* pool = (CThreadPool*)arg;
if (pool == NULL) return NULL;

while (1){
pthread_mutex_lock(&pool->queue_mutex);
while (pool->cur_queue_size == 0 && !pool->shutdown){
pthread_cond_wait(&pool->queue_cond, &pool->queue_mutex);
}

if (pool->shutdown){
pthread_mutex_unlock(&pool->queue_mutex);
pthread_exit(NULL);
}

assert(pool->cur_queue_size != 0);
assert(!pool->queue_job.empty());

pool->cur_queue_size--;
CJob* job = pool->queue_job.front();
pool->queue_job.pop_front();
pthread_mutex_unlock(&pool->queue_mutex);

(*job->callback_routine) (job->arg);
delete job;
}
}

CThreadPool::CThreadPool(int max_th_num) : cur_queue_size(0), shutdown(0), max_thread_num(max_th_num){
pthread_mutex_init(&queue_mutex, NULL);
pthread_cond_init(&queue_cond, NULL);
thread_vec = new pthread_t[max_thread_num];
for (int i = 0; i < max_thread_num; i++){
pthread_create(&thread_vec[i], NULL, thread_routine, (void*)this);
}
}

CThreadPool::~CThreadPool(){
if (shutdown) return;
shutdown = 1;
pthread_cond_broadcast(&queue_cond);
for (int i = 0; i < max_thread_num; i++)
pthread_join(thread_vec[i], NULL);
delete[] thread_vec;
for (list<CJob*>::iterator it = queue_job.begin(); it != queue_job.end(); ++it)
delete *it;
queue_job.clear();
pthread_mutex_destroy(&queue_mutex);
pthread_cond_destroy(&queue_cond);
}

int CThreadPool::pool_add_job(void* (*process)(void* arg), void* arg){
CJob* job = new CJob(process, arg);

pthread_mutex_lock(&queue_mutex);
queue_job.push_back(job);
cur_queue_size++;
pthread_mutex_unlock(&queue_mutex);
pthread_cond_signal(&queue_cond);
return 0;
}

利用curl下载单个瓦片图像的函数实现为:

void* job_process(void* ar)
{ //huCurl_3对应原来的job_process
CURL* curl;
CURLcode res;
JobInfo* ji = (JobInfo*)ar;
FILE* fp;
string full_path = ji->downfile;
fp = fopen(full_path.c_str(), "wb"); //在此统一打开文件

curl = curl_easy_init();
//curl_easy_setopt(curl, CURLOPT_FILE, );
curl_easy_setopt(curl, CURLOPT_URL, ji->url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L);
curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1L);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 30L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0"); //user-agent
curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1L);

res = curl_easy_perform(curl);
if (CURLE_OK != res){
printf("单个图像下载错误:[%d] %s\n", res, ji->url.c_str());
}
fclose(fp);

curl_easy_cleanup(curl);

//TODO: 回馈下载分片的完成情况....
Sleep(1);

return;
}

利用线程池下载函数为:

vector<JobInfo> ji_vec;
get_job_queue2(filesUrl, filesDown, ji_vec);

int num = ji_vec.size();//用来记录下载瓦片的个数

for (int i=0;i<num;i++)
{
//cout<<i<<":"<<num<<endl;
pool->pool_add_job(job_process, (void*)&ji_vec[i]);
}

// 下载等待结束
int waitnum = 0;
while(!check4done(filesDown))
{
waitnum = waitnum + 1;
cout<<"等待:"<<waitnum<<endl;
if (waitnum>10)
{
LogHandler("HuTilesMosaic.log", "网络拥堵?等待...");
}
if (waitnum>100)
{
LogHandler("HuTilesMosaic.log", "下载失败!");
delete pool;
return 1;
}
Sleep(10000);
}

delete pool;

该实现下载谷歌图像,在教育网垃圾网速下,1万幅瓦片下载时间1分钟以内,这比用简单的openMP快了不少!

该实现方式存在的问题主要是所有线程结束时的反馈得不到,这里采用的笨办法是隔5秒检查文件夹下下载的瓦片数量是否达到总数,没有下载完则再等5秒。。。

所以有谁能通过上述代码找到办法?将所有线程结束后join返回给主线程?

另外,目前下载的瓦片图像是保存在本地文件夹下的,然后再利用gdal进行图像镶嵌。这样硬盘反复读写的时间消耗便不能忽略。

所以下一步的优化是内存操作,curl下载到标准输出,gdal直接读取内存中的瓦片文件,再保存到硬盘的镶嵌结果tif图像中。

所以如何获得线程结束返回啊???再搞两天等有了结果再说吧。

目前下载支持EPSG4326经纬度与EPSG3857谷歌全球墨卡托投影。经纬度瓦片切图规则与天地图相同,从第一层开始切,第一层包含两个瓦片。谷歌全球墨卡托从第0层开始切,第0层一个瓦片。