线程池技术
在linux系统中,采用多线程机制可以实现服务器的并发请求,但对于高并发服务器而言,这里面存在一个致命的安全隐患。因为系统每创建一个线程,都会为该线程分配一定的系统资源,所以一个进程创建的子线程数是有限制的,如果在同一时刻有大量的客户端并发请求服务器,这时服务器的主线程就不断地创建子线程处理连接到来的客户端,这样系统的资源就慢慢消耗殆尽,如果此时还有其他客户端请求服务器,这时服务器就没有额外资源来创建线程来处理这个客户端到来的连接,这时候就会引发系统异常,甚至造成整个服务器系统崩溃。对此提出了线程池的解决方案,使高并发的服务器在有限资源的linux系统中得到实现在服务器的主线程中预先创建含有限个任务线程的线程池,一旦有客户请求到来就启动这些任务线程来处理客户端到来的连接,超出部分的客户就先在任务队列中等待,等任务线程处理完任务后成为空闲的任务线程,空闲出来的任务线程就可以转而执行等待队列中的任务。
为什么需要线程池?
目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。
我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。
T1:线程创建时间
T2:线程执行时间,包括线程的同步等时间
T3:线程销毁时间
那么我们可以看出,线程本身的开销所占的比例为(T1+T3) /(T1+T2+T3)。如果线程执行的时间很短的话,这比开销可能占到20%-50%左右。如果任务执行时间很频繁的话,这笔开销将是不可忽略的。
除此之外,线程池能够减少创建的线程个数。通常线程池所允许的并发线程是有上界的,如果同时需要并发的线程数超过上界,那么一部分线程将会等待。而传统方案中,如果同时请求数目为2000,那么最坏情况下,系统可能需要产生2000个线程。尽管这不是一个很大的数目,但是也有部分机器可能达不到这种要求。因此线程池的出现正是着眼于减少线程池本身带来的开销。线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。基于这种预创建技术,线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上,执行次数越多,每个任务所分担到的线程本身开销则越小,不过我们另外可能需要考虑进去线程之间同步所带来的开销。
一般线程池都具备下面几个组成部分:
线程池管理器:用于创建并管理线程池。
工作线程: 线程池中实际执行的线程。
任务接口: 尽管线程池大多数情况下是用来支持网络服务器,但是我们将线程执行的任务抽象出来,形成任务接口,从而是的线程池与具体的任务无关。
任务线程池: 任务线程是在线程池结构体变量初始化时启动的线程。具体到实现则可能是队列,链表之类的数据结构,其中保存执行线程。任务线程池中的任务线程如果检测到任务链表中的任务节点数不为零就从任务链表尾取出任务节点并执行相应的任务,同时让任务链表中的任务节点数减1。如果任务链表中的任务数为零,就调用pthread_cond_wait使任务线程挂起,等待主线程向任务链表中抛入一个任务并且调用pthread_cond_signal 再次唤醒空闲挂起的任务线程执行任务。
主线程初始化线程池结构体变量,在初始化线程池变量时一次性创建并且启动m个任务线程的线程池。线程池变量中有一个记录任务链表节点数的变量n,当主线程向线程的任务链表头中抛入一任务节点就让任务链表节点数加1,同时调用pthread_cond_signal 函数唤醒空闲挂起的任务线程。
下面是C++封装的线程池,其中locker.h封装了信号量,互斥锁与条件变量类
: /*************************************************************************
> File Name: threadpool.h
> Author: skctvc
> Mail: skctvc15@163.com
> Created Time: 2014年06月11日 星期三 16时10分29秒
************************************************************************/
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include<list>
#include<cstdio>
#include<exception>
#include<pthread.h>
#include"locker.h"
/*线程池类,将其定义为模板,T是任务类*/
template < typename T >
class threadpool
{
public:
/* 参数1是线程池中线程的数量,参数2是请求队列中最多允许的、等待处理的请求的数量 */
threadpool(int thread_number = 8, int max_requests = 20000);
~threadpool();
/* 往请求队列中添加任务*/
bool append(T* request);
private:
/* 工作线程运行的函数,它不断从工作队列中取出任务并执行之 */
static void *worker( void *arg );
void run();
private:
int m_thread_number; /* 线程池中的线程数 */
int m_max_requests; /* 请求队列最大请求数 */
pthread_t* m_threads; /* 描述线程池的数组,其大小为m_thread_number */
std::list< T* > m_workqueue;/* 请求队列 */
locker m_queuelocker; /* 请求队列锁 */
sem m_queuestat; /* 是否有线程需要处理 */
bool m_stop; /* 是否结束线程 */
};
template < typename T >
threadpool < T >::threadpool( int thread_number , int max_requests ) :
m_thread_number( thread_number ),m_max_requests( max_requests ),m_threads( NULL ),m_stop( NULL )
{
if(( thread_number <= 0 ) || max_requests <= 0 )
{
throw std::exception();
}
m_threads = new pthread_t[ m_thread_number ];
if( !m_threads )
{
throw std::exception();
}
/*创建thread_number个线程,并将他们都设置为脱离线程*/
for( int i = 0; i < thread_number; i++ )
{
if( pthread_create( m_threads + i,NULL, worker, this ) != 0 )
{
delete [] m_threads;
throw std::exception();
}
if( pthread_detach( m_threads[i] ) )
{
delete [] m_threads;
throw std::exception();
}
}
}
template < typename T >
threadpool< T >::~threadpool()
{
delete [] m_threads;
m_stop = true;
}
template < typename T >
bool threadpool< T >::append(T * request)
{
m_queuelocker.lock();
if( m_workqueue.size() > m_max_requests )
{
m_queuelocker.unlock();
return false;
}
m_workqueue.push_back( request );
m_queuelocker.unlock();
m_queuestat.post();
return true;
}
template< typename T >
void* threadpool< T >::worker( void *arg )
{
threadpool* pool = (threadpool *)arg;
pool->run();
return pool;
}
template< typename T >
void threadpool< T >::run()
{
while( !m_stop )
{
m_queuestat.wait();
m_queuelocker.lock();
if( m_workqueue.empty() )
{
m_queuelocker.unlock();
continue;
}
T* request = m_workqueue.front();
m_workqueue.pop_front();
m_queuelocker.unlock();
if( !request )
{
continue;
}
request->process();
}
}
#endif
事实上,线程池并不是万能的。它有其特定的使用场合。线程池致力于减少线程本身的开销对应用所产生的影响,这是有前提的,前提就是线程本身开销与线程执行任务相比不可忽略。如果线程本身的开销相对于线程任务执行开销而言是可以忽略不计的,那么此时线程池所带来的好处是不明显的,比如对于FTP服务器以及Telnet服务器,通常传送文件的时间较长,开销较大,那么此时,我们采用线程池未必是理想的方法,我们可以选择“即时创建,即时销毁”的策略。
总之线程池通常适合下面的几个场合:
(1)单位时间内处理任务频繁而且任务处理时间短。
(2)对实时性要求较高。如果接受到任务后在创建线程,可能满足不了实时要求,因此必须采用线程池进行预创建。
(3)必须经常面对高突发性事件,比如Web服务器,如果有足球转播,则服务器将产生巨大的冲击。此时如果采取传统方法,则必须不停的大量产生线程,销毁线程。此时采用动态线程池可以避免这种情况的发生。