如何实现一个简单的web服务器(二)——线程池

目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。
传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。

我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。

T1:线程创建时间
T2:线程执行时间,包括线程的同步等时间
T3:线程销毁时间

那么我们可以看出,线程本身的开销所占的比例为(T1+T3) / (T1+T2+T3)。如果线程执行的时间很短的话,这比开销可能占到20%-50%左右。如果任务执行时间很频繁的话,这笔开销将是不可忽略的。

      除此之外,线程池能够减少创建的线程个数。通常线程池所允许的并发线程是有上界的,如果同时需要并发的线程数超过上界,那么一部分线程将会等待。而传统方案中,如果同时请求数目为2000,那么最坏情况下,系统可能需要产生2000个线程。尽管这不是一个很大的数目,但是也有部分机器可能达不到这种要求。

      因此线程池的出现正是着眼于减少线程池本身带来的开销。线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。
      基于这种预创建技术,线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上,执行次数越多,每个任务所分担到的线程本身开销则越小,不过我们另外可能需要考虑进去线程之间同步所带来的开销。

 

在这里实现一个基于半同步/半反应堆模式的线程池,该线程池通用性比较高,主线程一般往工作队列中加入任务,然后工作线程等待后并通过竞争关系从工作队列中取出任务并且执行。而且应用到服务器程序中的话要保证客户请求都是无状态的,因为同一个连接上的不同请求可能会由不同的线程处理。

#ifndef _PTHREAD_POOL_
#define _PTHREAD_POOL_
 
#include "locker.h"
#include <queue>
#include <stdio.h>
#include <exception>
#include <errno.h>
#include <pthread.h>
#include <iostream>
 
template<class T>
class threadpool
{
private:
    int thread_number;  //线程池的线程数
    pthread_t *all_threads;   //线程数组
    std::queue<T *> task_queue; //任务队列
    mutex_locker queue_mutex_locker;  //互斥锁
    cond_locker queue_cond_locker; //cond
    bool is_stop; //是否结束线程
public:
    threadpool(int thread_num = 20);
    ~threadpool();
    bool append_task(T *task);
    void start();
    void stop();
private:
    //线程运行的函数。执行run()函数
    static void *worker(void *arg);
    void run();
    T *getTask();
};
 
template <class T>
threadpool<T>::threadpool(int thread_num):
	thread_number(thread_num),is_stop(false), all_threads(NULL)
{
    if(thread_num <= 0)
	printf("threadpool can't init because thread_number = 0");
 
    all_threads = new pthread_t[thread_number];
    if(all_threads == NULL)
    	printf("can't init threadpool because thread array can't new");
}
 
template <class T>
threadpool<T>::~threadpool()
{
    delete []all_threads;
    stop();
}
 
template <class T>
void threadpool<T>::stop()
{
        is_stop = true;
        queue_cond_locker.broadcast();
}
 
template <class T>
void threadpool<T>::start()
{
    for(int i = 0; i < thread_number; ++i)
    {
	if(pthread_create(all_threads + i, NULL, worker, this) != 0)
	{//创建线程失败,清除成功申请的资源并抛出异常
	    delete []all_threads;
	    throw std::exception();
	}
	if(pthread_detach(all_threads[i]))
	{//将线程设置为脱离线程,失败则清除成功申请的资源并抛出异常
	    delete []all_threads;
	    throw std::exception();
	}
    }
}
//添加任务进入任务队列
template <class T>
bool threadpool<T>::append_task(T *task)
{   //获取互斥锁
    queue_mutex_locker.mutex_lock();
    
    bool is_signal = task_queue.empty();
    //添加进入队列
    task_queue.push(task);
    queue_mutex_locker.mutex_unlock();
    //唤醒等待任务的线程
    if(is_signal)
    {
            queue_cond_locker.signal();
    }
    return true;
}
 
template <class T>
void *threadpool<T>::worker(void *arg)
{
    threadpool *pool = (threadpool *)arg;
    pool->run();
    return pool;
}
 
template <class T>
T* threadpool<T>::getTask()
{
    T *task = NULL;
    queue_mutex_locker.mutex_lock();
    if(!task_queue.empty())
    {
        task = task_queue.front();
        task_queue.pop();
    }
    queue_mutex_locker.mutex_unlock();
    return task;
}
 
template <class T>
void threadpool<T>::run()
{
    while(!is_stop){
        T *task = getTask();
        if(task == NULL)
                queue_cond_locker.wait();
        else
	{
                task->doit();
		delete task;
	}
    }
}
 
#endif

事实上,线程池并不是万能的。它有其特定的使用场合。线程池致力于减少线程本身的开销对应用所产生的影响,这是有前提的,前提就是线程本身开销与线程执行任务相比不可忽略。如果线程本身的开销相对于线程任务执行开销而言是可以忽略不计的,那么此时线程池所带来的好处是不明显的,比如对于FTP服务器以及Telnet服务器,通常传送文件的时间较长,开销较大,那么此时,我们采用线程池未必是理想的方法,我们可以选择“即时创建,即时销毁”的策略。
总之线程池通常适合下面的几个场合:
(1) 单位时间内处理任务频繁而且任务处理时间短
(2) 对实时性要求较高。如果接受到任务后在创建线程,可能满足不了实时要求,因此必须采用线程池进行预创建。
(3) 必须经常面对高突发性事件,比如Web服务器,如果有足球转播,则服务器将产生巨大的冲击。此时如果采取传统方法,则必须不停的大量产生线程,销毁线程。此时采用动态线程池可以避免这种情况的发生。

 

因为采用了线程池,就相当于用了多线程编程,此时就需要考虑各个线程对公共资源的访问的限制,因为方便之后的代码采用了三种包装机制,分别是信号量的类,互斥锁的类和条件变量的类。

#ifndef _LOCKER_H_
#define _LOCKER_H_
 
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
 
/*信号量的类*/
class sem_locker
{
private:
    sem_t m_sem;
 
public:
    //初始化信号量
    sem_locker()
    {
	if(sem_init(&m_sem, 0, 0) != 0)
	    printf("sem init error\n");
    }
    //销毁信号量
    ~sem_locker()
    {
	sem_destroy(&m_sem);
    }
 
    //等待信号量
    bool wait()
    {
	return sem_wait(&m_sem) == 0;
    }
    //添加信号量
    bool add()
    {
	return sem_post(&m_sem) == 0;
    }
};
 
 
/*互斥 locker*/
class mutex_locker
{
private:
    pthread_mutex_t m_mutex;
 
public:
    mutex_locker()
    {
    	if(pthread_mutex_init(&m_mutex, NULL) != 0)
	    printf("mutex init error!");
    }
    ~mutex_locker()
    {
	pthread_mutex_destroy(&m_mutex);
    }
 
    bool mutex_lock()  //lock mutex
    {
	return pthread_mutex_lock(&m_mutex) == 0;
    }
    bool mutex_unlock()   //unlock
    {
	return pthread_mutex_unlock(&m_mutex) == 0;
    }
};
 
/*条件变量 locker*/
class cond_locker
{
private:
    pthread_mutex_t m_mutex;
    pthread_cond_t m_cond;
 
public:
    // 初始化 m_mutex and m_cond
    cond_locker()
    {
	if(pthread_mutex_init(&m_mutex, NULL) != 0)
	    printf("mutex init error");
	if(pthread_cond_init(&m_cond, NULL) != 0)
	{   //条件变量初始化是被,释放初始化成功的mutex
	    pthread_mutex_destroy(&m_mutex);
	    printf("cond init error");
	}
    }
    // destroy mutex and cond
    ~cond_locker()
    {
	pthread_mutex_destroy(&m_mutex);
	pthread_cond_destroy(&m_cond);
    }
    //等待条件变量
    bool wait()
    {
	int ans = 0;
	pthread_mutex_lock(&m_mutex);
	ans = pthread_cond_wait(&m_cond, &m_mutex);
	pthread_mutex_unlock(&m_mutex);
	return ans == 0;
    }
    //唤醒等待条件变量的线程
    bool signal()
    {
	return pthread_cond_signal(&m_cond) == 0;
    }
 
    //唤醒all等待条件变量的线程
    bool broadcast()
    {
            return pthread_cond_broadcast(&m_cond) == 0;
    }
};
 
#endif

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值