设计时要区分:面向多消息的多线程还是面向多连接的多线程。
一、服务器线程模型分类:
(1)循环模式:只能同时监听一个事件源,来一个连接生成一个线程。这只能是多线程,不能算线程池,需要和controler/worker配合使用。这种模型当客户端连接数快速增长是就会出现性能瓶颈。因为创建线程需要浪费大量的时间。
(2)反应模式(reactor):通过多路复用select,可以同时监听多个事件源。这也只能是多线程,不能算线程池,需要和controler/worker配合使用
(3)Controler/Workers(适合多消息的处理):主控线程预先生成很多线程,并负责事件源,及worker线程的启动和同步。当到来一个连接时,启动一个阻塞队列中的工作线程负责处理该连接。在linux中可以通过条件变量来实现。可能出现的性能问题:controler把工作交给一个worker线程的时候可能需要上下文切换,包括数据拷贝。
(4)Leader/Followers(LF)(适合多连接的处理):所有的线程都是对等的。该模型所有线程会有三种身份中的一种:leader和follower,以及一个干活中的状态proccesser。它的基本原则就是,永远最多只有一个leader。而所有follower都在等待成为leader。线程池启动时会自动产生一个Leader负责等待网络IO事件,当有一个事件产生时,Leader线程首先通知一个Follower线程将其提拔为新的Leader,然后自己就去干活了,去处理这个网络事件,处理完毕后加入Follower线程等待队列,等待下次成为Leader。该模型的优点是解决了controler/workers不足,Leader-Followers模型可以增强CPU高速缓存相似性,及消除动态内存分配和线程间的数据交换。实现方式:让所有线程去竞争一个mutex,得到的成为leader,负责阻塞连接,当接收到连接时,解除mutex,并负责处理该连接。处理完之后又去竞争mutex,并阻塞。是不是太简单。
线程池的作用:提高消息(任务)响应的实时性、提高任务执行的速度。
二、线程池的注意事项
(1)线程池大小。多线程应用并非线程越多越好,需要根据系统运行的软硬件环境以及应用本身的特点决定线程池的大小。一般来说,如果代码结构合理的话,线程数目与CPU 数量相适合即可。如果线程运行时可能出现阻塞现象,可相应增加池的大小;如有必要可采用自适应算法来动态调整线程池的大小,以提高CPU 的有效利用率和系统的整体性能。
(2)并发错误。多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避免死锁现象的发生。
(3)线程泄漏。这是线程池应用中一个严重的问题,当任务执行完毕而线程没能返回池中就会发生线程泄漏现象。
三、Controler/Workers线程池模型
一个典型的Controler/Workers线程池,应该包括如下几个部分:
1、线程池管理器(ThreadPool),用于启动、停用,管理线程池
2、工作线程(WorkThread),线程池中的线程
3、请求接口(WorkRequest),创建请求对象,以供工作线程调度任务的执行
4、请求队列(RequestQueue),用于存放和提取请求(实现获取请求与插入请求的互斥)
5、结果队列(ResultQueue),用于存储请求执行后返回的结果
线程池管理器:通过添加请求的方法(putRequest)向请求队列(RequestQueue)添加请求,这些请求事先需要实现请求接口,即传递工作函数、参数、结果处理函数、以及异常处理函数。之后初始化一定数量的工作线程,这些线程通过轮询的方式不断查看请求队列(RequestQueue),只要有请求存在,则会提取出请求,进行执行。然后,线程池管理器调用方法(poll)查看结果队列(resultQueue)是否有值,如果有值,则取出,调用结果处理函数执行。通过以上讲述,不难发现,这个系统的核心资源在于请求队列和结果队列,工作线程通过轮询requestQueue获得任务,主线程通过查看结果队列,获得执行结果。因此,对这个队列的设计,要实现线程同步,以及一定阻塞和超时机制的设计,以防止因为不断轮询而导致的过多cpu开销。
例子:条件变量实现线程池模型(不带结果队列)
条件变量实现线程池模型基本原理:工作线程默认情况下是阻塞在 pthread_cond_wait() 系统调用下的,如果有任务到来,我们可用使用 pthread_cond_singal() 来唤醒一个处于阻塞状态的线程,这样这个线程就可以执行 mc_thread_pool_get_task() 来取得一个任务,并调用相应的回调函数。
typedef struct _thread_pool_t
{
pthread_mutex_t queue_lock ;//任务锁
pthread_cond_t task_cond ;//条件变量
list_t * tasks ;// 任务队列
pthread_t * pthreads ;//线程池
int isdestoried;
int workersnum ;
char ready ;//指定当前是否存在任务(0,1),pthreads任务队列是否可用
thread_task_handler thread_pool_task_handler;//回调函数,线程执行体=mc_thread_entry
}thread_pool_t;
void *thread_entry( void *args )
{
int fd = *(int *)args ;
do_handler_fd( fd );
}
void mc_thread_pool_ini( mc_thread_pool_t * par_tp , int workersnum ,thread_task_handler par_handler ) //创建线程池
{
int err ;
//par_tp = ( thread_pool_t *)malloc( sizeof(thread_pool_t) );
if( par_tp == NULL )
{
fprintf( stderr , "thread_pool_t malloc\n");
return ;
}
par_tp->workersnum = workersnum ;
pthread_mutex_init( &par_tp->queue_lock ,NULL );
pthread_cond_init(&par_tp->task_cond , NULL );
/*
par_tp->queue_lock = PTHREAD_MUTEX_INITIALIZER ;
par_tp->task_cond = PTHREAD_COND_INITIALIZER ;
*/
par_tp->tasks = mc_listcreate() ;
if( par_tp->tasks == NULL )
{
fprintf( stderr , "listcreate() error\n");
//free( par_tp ) ;
return ;
}
par_tp->pthreads = ( pthread_t *)malloc( sizeof( pthread_t )*workersnum );
if( par_tp->pthreads == NULL )
{
fprintf( stderr , "pthreads malloc\n");
//free( par_tp );
mc_freelist( par_tp->tasks ) ;
return NULL ;
}
int i = 0 ;
for( ; i < workersnum ; i++ )
{
fprintf(stderr,"start to create threads\n");
err = pthread_create(&(par_tp->pthreads[i]),NULL,mc_thread_entry,NULL) ;
if( err == -1 )
{
fprintf( stderr , "pthread_create error\n");
//free( par_tp );
mc_freelist( par_tp->tasks ) ;
free(par_tp->pthreads) ;
}
}
par_tp->thread_pool_task_handler = par_handler ;
par_tp->ready = 0 ;
fprintf(stderr,"successed to create threads\n");
}
static void *mc_thread_entry( void *args )//工作线程
{
void * task ;
for(;;)
{
pthread_mutex_lock( &mc_global_threads_pool.queue_lock ) ;
fprintf(stderr, " locked to wait task\n");
while( mc_global_threads_pool.ready == 0 )
{
pthread_cond_wait( &mc_global_threads_pool.task_cond , &mc_global_threads_pool.queue_lock ) ;
}
task = mc_thread_pool_get_task() ;
fprintf(stderr, "get a task and ready to unlock \n");
pthread_mutex_unlock( &mc_global_threads_pool.queue_lock ) ;
mc_global_threads_pool.thread_pool_task_handler( task ) ;
}
}
void mc_thread_pool_add_task(void *task , size_t tasksize ) //写任务队列{
pthread_mutex_lock( &mc_global_threads_pool.queue_lock );
fprintf( stderr ,"thread locked and append to list\n");
mc_list_append( mc_global_threads_pool.tasks , task , tasksize ) ;
pthread_mutex_unlock( &mc_global_threads_pool.queue_lock );
fprintf( stderr ,"thread unlocked and successed append to list\n");
mc_global_threads_pool.ready = 1 ;
if( mc_global_threads_pool.ready == 1 )
{
fprintf( stderr ,"signal to threads\n");
pthread_cond_signal( &mc_global_threads_pool.task_cond ) ;
}
}
void *mc_thread_pool_get_task()
{
void * ret_task ;
ret_task = mc_getnode_del( mc_global_threads_pool.tasks , 0 );
if( ret_task == NULL )
{
fprintf(stderr,"get node_del error\n");
}
fprintf( stderr ," got a task\n");
mc_global_threads_pool.ready = 0 ;
if( ret_task == NULL )
{
fprintf(stderr, "getnode_del error\n");
return NULL ;
}
else
return ret_task ;
}
int main()
{
mc_thread_task_t ltask;
ltask.task_num = 1 ;
fprintf(stderr,"begin to ini pool\n");
mc_thread_pool_ini( &mc_global_threads_pool , 20 , my_thread_task_handler );
mc_thread_pool_add_task( <ask , sizeof(mc_thread_task_t) );
int i = 0 ;
for(;i < 10000; i++)
{
ltask.task_num = i ;
mc_thread_pool_add_task( <ask , sizeof(mc_thread_task_t) );
sleep(1);
}
return 0;
}
四、Leader/Followers线程池模型
1.优点:解决了把工作交给别一个线程的时候的上下文切换,包括数据拷贝。可以增强CPU高速缓存相似性,及消除动态内存分配和线程间的数据交换。
2.缺点:不过,这种模式在处理短暂的、原子的、反复的和基于事件的动作上可以取得明显的性能提升,比如接收和分发网络事件或者向数据库存储大量数据记录。事件处理程序所提供的服务越多,其体积也就越大,而处理一个请求所需的时间越长,池中的线程占用的资源也就越多,同时也需要更多的线程。相应的,应用程序中其它功能可用的资源也就越少,从而影响到应用程序的总体性能、吞吐量、可扩展性和可用性。应该根据处理事件的时间合理的分配线程的数量。
3.工作原理:所有线程会有三种身份中的一种:leader和follower,以及一个干活中的状态proccesser。它的基本原则就是,永远最多只有一个leader。而所有follower都在等待成为leader。线程池启动时会自动产生一个Leader负责等待网络IO事件,当有一个事件产生时,Leader线程首先通知一个Follower线程将其提拔为新的Leader,然后自己就去干活了,去处理这个网络事件,处理完毕后加入Follower线程等待队列,等待下次成为Leader。
4.代码演示
首先用一段简单的代码演示一下整个角色转换的过程。由于同一时刻只有一个leader,用一个互斥量就可以解决了。每个线程一直在做如下4个步骤循环:
public class WorkThread
{
public static Mutex mutex = new Mutex();
public void start()
{
while (true)
{
// 等待成为leader
waitToLeader();
// 用select或epoll等方式等待消息处理
simulateReactor();
// 产生下一个leader
promoteNewLeader();
// 处理消息
simulateDojob();
}
}
private void simulateDojob()
{
…
}
private void promoteNewLeader()
{
Console.WriteLine(Thread.CurrentThread.Name + ": Release leadership to others..");
mutex.ReleaseMutex();
}
private void simulateReactor()
{
…
}
private void waitToLeader()
{
Console.WriteLine(Thread.CurrentThread.Name + ": Waiting to be Leader..");
mutex.WaitOne();
}
}