目录
前言:
本文基于C++11提供一个简单明了的“多线程存图队列”的模板,使用opencv处理图像,结合线程池调用,当然不熟悉线程池下面也给出了替代方案,C++11 thread创建单个子线程。文末也提供了相关知识的补充文章。
我们也可以基于结构体提供更多信息,增加更多对图像的处理操作。咱们还是先上源码再讲设计,建议结合设计再看代码哦。
1. 源码
SaveImgWorker.h
// SaveImgWorker.h
#pragma once
// opencv操作图像数据
#include "opencv2/opencv.hpp"
#include <string>
#include <mutex>
#include <queue>
/**
* 线程池.
* 也可以使用C11 thread()开辟线程替换线程池,但这种便会频繁开辟释放线程,耗能增大..
*/
class ThreadPool;
typedef struct _ImageInfo
{
cv::Mat image;
// double scale;
// string fileName;
// ...
}ImageInfo;
class SaveImgWorker
{
public:
SaveImgWorker();
~SaveImgWorker();
public:
/**
* 线程调用接口.
* 静态函数不用传入this指针,适合异步调用.
*/
static void runSaving(void* pUser, void* args);
/**
* 图像入队.
*/
void enQueue(const ImageInfo& info);
/**
* 存图操作.
*/
void run();
//=====================启动子线程前的操作=================//
/**
* 准备操作,设置存图路径.
*/
bool setPath(std::string path);
/**
* 准备操作,重置队列.
*/
void reset();
/**
* 准备操作,初始化线程池索引.
*/
void initThreadPool(ThreadPool* threadPool);
protected:
std::mutex m_mutexRuning;// 状态锁
std::mutex m_mutexQueue;// 队列锁
std::queue<ImageInfo> m_ImageInfoQueue;
std::string m_sFilePath;// 存图地址
uint64_t m_nSaveNum;// 累计存图数
bool m_bIsRunning;// 存储线程运行状态,同时只能有一个线程进行存图
ThreadPool* m_pThreadPool;
};
SaveImgWorker.cpp
// SaveImgWorker.cpp
#include "SaveImgWorker.h"
#include "ThreadPool.h"// 未提供线程池代码
#include <thread>
using namespace std;
SaveImgWorker::SaveImgWorker()
{
m_pThreadPool = nullptr;
m_bIsRunning = false;
}
SaveImgWorker::~SaveImgWorker()
{
while (!m_ImageInfoQueue.empty())
m_ImageInfoQueue.pop();
m_pThreadPool = nullptr;
}
void SaveImgWorker::runSaving(void* pUser, void* args)
{
SaveImgWorker* saveWorker = (SaveImgWorker*)pUser;
if (saveWorker != nullptr)
saveWorker->run();
}
// 通常是主线程执行入队操作
void SaveImgWorker::enQueue(const ImageInfo& info)
{
m_mutexQueue.lock();
m_ImageInfoQueue.push(info);
m_mutexQueue.unlock();
unique_lock<mutex> lock(m_mutexRuning);
// 若未在存图中,则启动子线程执行
if (!m_bIsRunning && m_pThreadPool != nullptr)
{
lock.unlock();
// 线程池执行存图操作
m_pThreadPool->pushJob(runSaving, this, nullptr, 0);
// 不了解线程池也可以如下替换,创建单个线程:
//thread saveJob(runSaving, this, nullptr);
//saveJob.detach();
}
}
// 存图操作,只能同时占用一个线程
void SaveImgWorker::run()
{
m_mutexRuning.lock();
m_bIsRunning = true;
m_mutexRuning.unlock();
cv::Mat mat;
ImageInfo imageInfo;
unique_lock<mutex> lock(m_mutexQueue);
while (!m_ImageInfoQueue.empty())
{
lock.unlock();
// 已判断非空,入队出队不冲突
imageInfo = m_ImageInfoQueue.front();
m_ImageInfoQueue.pop();
mat = imageInfo.image;
// 存图及图像处理操作
if (m_sFilePath != "")
cv::imwrite((m_sFilePath + std::to_string(m_nSaveNum++) + ".bmp"), mat);
lock.lock();
}
m_mutexRuning.lock();
m_bIsRunning = false;
m_mutexRuning.unlock();
}
bool SaveImgWorker::setPath(string path)
{// 判定文件夹是否存在
if (true)
{
m_sFilePath = path + "/image";
return true;
}
return false;
}
void SaveImgWorker::reset()
{
m_nSaveNum = 0;
while (!m_ImageInfoQueue.empty())
m_ImageInfoQueue.pop();
}
void SaveImgWorker::initThreadPool(ThreadPool* threadPool)
{
m_pThreadPool = threadPool;
}
2. 结构体设计
入队的数据为包含图像数据的结构体ImageInfo,要处理更多的图像操作,当然要提供更多的数据,例如缩放比例,文件名......
typedef struct _ImageInfo
{
cv::Mat image;
// double scale;
// string fileName;
// ...
}ImageInfo;
注意,cv::Mat image 实际只存储了输入图像的地址,并未对图像数据进行深拷贝。
3. 多线程设计
入队操作enQueue()通常是在主线程进行的,而存图操作run()则是在线程池ThreadPool或子线程saveJob进行。同时通过enQueue管理存图任务只交代给一个线程去处理,要注意加锁。实际上,这里主线程入队操作是很快的,因为并没有拷贝图像,而子线程存图是极为耗时的。
结语:
补充相关知识
QT 一个简明的多线程存图队列_qt队列显示图片-CSDN博客