我们通过创建 std::thread 对象来对线程进行管理。在一些情况下,这种方式不可行,因为需要在线程的整个生命周期中对其进行管理,并根据硬件来确定线程数量,等等。另一种情况是,当使用多线程来解决某个问题时,在某个条件达成的时候,可以提前结束。
管理线程和任务的机制,两个角度介绍线程池。
一、简单的线程池
定义:管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。作为最简单的线程池,其拥有固定数量的工作线程(通常工作线程数量std::thread::hardware_concurrency() 相同)。当工作需要完成时,可以调用函数将任务挂在任务队列中。每个工作线程都会从任务队列上获取任务,然后执行这个任务,执行完成后再回来获取新的任务。
简单的线程池:
class thread_pool
{
std::atomic_bool done;
thread_safe_queue<std::function<void()> > work_queue; // 1 线程安全队列管理任务队列
std::vector<std::thread> threads; // 2 工作线程
join_threads joiner; // 3 来汇聚所有线程
void worker_thread()
{
while(!done) // 4
{
std::function<void()> task;
if(work_queue.try_pop(task)) // 5 从队列上获取任务
{
task(); // 6 执行任务,可在此加上线程数目增加语句。完成任务,增加一个空闲线程数目
}
else
{
std::this_thread::yield(); // 7 让线程休息
}
}
}
public:
thread_pool(): //列表初始化类
done(false),joiner(threads)
{
unsigned const
thread_count=std::thread::hardware_concurrency(); // 8 获取硬件支持并发数
try
{
for(unsigned i=0;i<thread_count;++i)
{
threads.push_back(
std::thread(&thread_pool::worker_thread,this)); // 9 线程执行于此,每个线程都会执行获取任务的函数。但是,本简单线程池不支持线程的回收循环利用
} //可以,在回收线程那里加上--thread_count;从而可以多次循环
}
catch(...)
{
done=true; // 10
throw;
}
}
~thread_pool()
{
done=true; // 11
}
template<typename FunctionType>
void submit(FunctionType f)
{
work_queue.push(std::function<void()>(f)); // 12 将函数f包装成一个std::function<void()>实例,推入队列之中
}
};
CPU密集型的程序要(cpu个数个+1)个线程就行了,I/O密集型,因为I/O操作不需要访问CPU,所以可以创建多个线程(2*NUM_cpu).
另一个思路,先创建一定量的线程,当忙碌线程达到总线程的80%时,创建新的线程。
总之线程池通常适合下面的几个场合:
(1) 单位时间内处理任务频繁而且任务处理时间短
(2) 对实时性要求较高。如果接受到任务后在创建线程,可能满足不了实时要求,因此必须采用线程池进行预创建。
(3) 必须经常面对高突发性事件,比如Web服务器,如果有足球转播,则服务器将产生巨大的冲击。此时如果采取传统方法,则必须不停的大量产生线程,销毁线程。此时采用动态线程池可以避免这种情况的发生。