最近实现线程池踩了不少坑…记录如下:
问题描述
线程池创建了两个线程,放两个任务进去,发现任务没执行.
例如:
void task()
{
std::cout<<"hello world"<<std::endl;
}
void task2(int i)
{
std::cout<<"hello world "<<i<<std::endl;
}
int main()
{
threadpool p(10,2);//10 queuesize 2 threads
p.start();
p.addtask(task);
p.addtask(boost::bind(task2,3));
return 0;
}
程序运行之后没有任何打印在终端,但是gdb 和strace都是有输出的.
解决
一共发现两个问题最终解决之:
先看线程池的实现
threadpool::task threadpool::gettask()
{
mutexlockguard lock(mutex_);
while(queue_.empty()&&isstarted_)
{
notempty_.wait();
}
task t;
if(!queue_.empty())//这里非常重要
//之前我写的是 if(isstarted_)
{
assert(!queue_.empty());
t=queue_.front();
queue_.pop();
notfull_.notify();
}
return t;
}
//这是线程执行体
//我们可以看到决定线程生命周期的是isstarted_这个变量
//当isstarted_变为false之后线程退出,pthread_join返回
void threadpool::runinthread()
{
while(isstarted_)
{
task t(gettask());//will blocked until "add" ops
if(t)
{
t();//run
}
}
}
这里解释了线程池的执行逻辑…我们有一个内部的方法gettask
将从队列中取任务,如果线程还没启动或者没有任务,这个函数将阻塞…
到这里都没什么问题,因为我们会在线程池析构的时候,对等待的条件变量notify,并且将isstarted赋值为false,此时gettask()将返回一个空的task,循环将结束,pthread_join将顺利执行….
//析够函数
threadpool::~threadpool()
{
if(isstarted_)
{
stop();
}
}
void threadpool::stop()
{
{
mutexlockguard lock(mutex_);
isstarted_ = false;
notempty_.notifyall(); //激活所有的线程
}
for_each(threads_.begin(),
threads_.end(),
boost::bind(&thread::join, _1));
}
一切都看起来那么的优雅美好,但是却忽略一个问题:
如果主线程执行得太快,那么很有可能工作线程还没有拿到任务,isstarted_就为false了 条件变量也唤醒了,线程拿到一个空任务,什么都不做就退出了,pthread_join
也就返回了,线程没有做任何事情就退出了….
试着发现这一切发生的原因: isstarted_
的赋值不是线程安全的….但是上面的代码明明是用mutexlock进行保护了?
wait调用自动释放锁,在主线程中add一个任务,notify线程,wait返回又会加上锁,析够函数不应该拿到锁修改临界区变量…
这个问题在https://github.com/chenshuo/muduo/issues/276
问题二
同样是线程没执行就退出了,这个比较简单,发现线程还没有start就被析够了,简单的sleep就能解决,不过sleep不能做同步原语,因为最好的解决办法还是用countdownlatch…
据我了解countdownlatch用法有两个:
1.每个任务进行countdown操作,初始化时count为任务数
countdownlatch latch(2);
void task()
{
std::cout<<"hello world"<<std::endl;
latch.countdown();
}
void task2(int i)
{
std::cout<<"hello world "<<i<<std::endl;
latch.countdown();
}
int main()
{
threadpool p(10,2);//10 queuesize 2 threads
p.start();
p.addtask(task);
p.addtask(boost::bind(task2,3));
p.addtask(boost::bind(&countdownlatch::countdown,&latch));
latch.wait();
return 0;
}
2.count初始化为1,在程序结束前wait,左右类似于一个barrier
void task()
{
std::cout<<"hello world"<<std::endl;
}
void task2(int i)
{
std::cout<<"hello world "<<i<<std::endl;
}
int main()
{
threadpool p(10,2);//10 queuesize 2 threads
countdownlatch latch(1);
p.start();
p.addtask(task);
p.addtask(boost::bind(task2,3));
p.addtask(boost::bind(&countdownlatch::countdown,&latch));
latch.wait();
return 0;
}