这几天闲着没事,参考lsd-slam的线程池,做了一个更加泛化的版本,用的c++11的线程库,个人感觉还是比较强大的,拿出来和大家分享一下,代码在git上有托管,这是最新发布的1.2版本:ThreadPool
这个东西是使用其实比较简单,只需要在项目里面加入ThreadPool.h头文件就可以正常使用,注意编译的时候打开-std=c++11编译flag以及-Wl,–no-as-needed链接flag
只有,你需要做的事情是定义一个ThreadPool的对象,这个对象定义有两个版本
ThreadPool<type> threads(threadNum);
ThreadPool<type pointer> threads(threadNum);
即分别是类型版本以及指针类型版本,之后请定义一个 void (*)(void*)
类型的函数,作为工作函数传给线程池,对于每个线程,可以传入不同的函数,这个函数也可以是类的成员函数,但是你需要在传入这个函数指针的同时,传入对象的指针,第三个参数如果类型是对象,那么直接传引用,如果是指针类型,那么请传入相应的指针
threads.reduce(index, workfunc, obj);
threads.reduce(index, workfunc, &obj);
threads.reduce(index, &someType::workfunc, &someTypeObj, obj);
threads.reduce(index, &someType::workfunc, &someTypeObj, &obj);
总共以上4种,注意第一个定义对应的是第一种和第三种,第二个定义对应的是第二种和第四种,那么指针类型定义和类型定义有啥区别呢,我是这么设计的
ThreadPool
这种定义是用来处理对象的,什么意思呢,就是说你可以在外部定义一个结构体,将你函数的输入和输出都放在这个结构体里面,然后分线程进行处理struct Data {
cv::Mat input;
cv::Mat *outPut;
Data() {}
~Data() {}
Data(Data& data) {
input = data.input;
outPut = data.outPut;
}
const Data& operator= (Data &data) {
input = data.input;
//cv::imshow("show", input);
outPut = data.outPut;
return *this;
}
};
void testOpt(void*data_)
{
Data* data = (Data*)data_;
*(data->outPut) = data->input.clone();
}
....
Data data[4];
ThreadPool thread(4);
for(int i = 0; i < 4; ++i) {
char buff[32];
memset(buff, 0, 32);
sprintf(buff, "data/%d.jpg", i + 1);
data[i].input = cv::imread(cv::String(buff));
data[i].outPut = new cv::Mat;
}
for(int i = 0; i < 4; ++i) {
thread.reduce(i, testOpt, data[i]);
}
while(!thread.isSynchronize());
对于如果是指针,这个设计类似于cuda设计,就是可以对一长串内存分段操作,像这样
const COL = 12;
const ROW = 12;
void workFunc2(void*data_) {
double *data = (double*)data_;
for(int i = 0; i < COL; ++i)
deal(data[i]);
}
double data2[ROW][COL];
ThreadPool<double*> threads2(ROW);
for(int i = 0; i < 12; ++i)
threads2.reduce(i, workFunc2, data2[i]);
这东西实现起来实际上挺简单的,就没几行代码,一会儿就写完了,主要思路我大概说一下
for(int i = 0; i < maxThreadNum; ++i) {
workFunc[i] = defaultFunc;
running[i] = false;
threads[i] = std::thread(&ThreadPool::workLoop, this, i);
}
这段是构造函数里面的,实际上就是把类里面的workLoop函数放到线程里面跑,之后在workLoop里面用condition_variable来对线程进行管理
void workLoop(int idx) {
std::unique_lock lock(Mutex[idx]);
while(true)
{
if(running[idx] == false)
conditionVal[idx].wait(lock);
workFunc[idx]((void*)&x[idx]);
++sleepThreadNum;
workFunc[idx] = defaultFunc;
running[idx] = false;
}
}
简单说就是如果running==false那么线程就等待,否则workFuncidx;调用通过reduce传进来的函数,调用完成之后,将函数设置回默认函数,最后running==false;
bool reduce(int idx, WorkFunc f, typename Trait::reference x) {
if(idx < 0 || idx >= maxThreadNum)
return false;
sourceMutex[idx].lock();
--sleepThreadNum;
workFunc[idx] = f;
this->x[idx] = x;
running[idx] = true;
sourceMutex[idx].unlock();
conditionVal[idx].notify_all();
return true;
}
这东西是reduce,实际上就是吧传入函数和数据保存起来,之后 conditionVal[idx].notify_all();唤起线程,很简单的操作就实现了这些个功能