线程池基础

20 篇文章 0 订阅

为什么要有线程池

没有线程池会出现的问题。

   大多数的网络服务器,包括Web服务器都具有一个特点,就是单位时间内必须处理数目巨大的连接请求,但是处理时间却是比较短的。在传统的多线程服务器模型中是这样实现的:一旦有个请求到达,就创建一个新的线程,由该线程执行任务,任务执行完毕之后,线程就退出。这就是”即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数非常频繁,那么服务器就将处于一个不停的创建线程和销毁线程的状态。这笔开销是不可忽略的,尤其是线程执行的时间非常非常短的情况。

线程池的思路

  线程池就是为了解决上述问题的,它的实现原理是这样的:在应用程序启动之后,就马上创建一定数量的线程,放入空闲的队列中。这些线程都是处于阻塞状态,这些线程只占一点内存,不占用CPU。当任务到来后,线程池将选择一个空闲的线程,将任务传入此线程中运行。当所有的线程都处在处理任务的时候,线程池将自动创建一定的数量的新线程,用于处理更多的任务。执行任务完成之后线程并不退出,而是继续在线程池中等待下一次任务。当大部分线程处于阻塞状态时,线程池将自动销毁一部分的线程,回收系统资源。

线程池的设计思路

1、现实生活中的例子

   以银行服务大厅为例。可以把银行的服务大厅看作一个线程池。 在大厅椅子上等待窗口有空位的储户们视为(任务队列), 柜台窗口里的柜员视为执行任务的线程(执行队列)。银行服务大厅还有一个非常重要的组成就是提示灯牌,这个灯牌会协调柜员和储户的配合,保证每个储户只会同时被一个柜员服务,保证只要有一个柜员空闲且有储户正在等待的时候这个柜员一定会服务这个储户。银行服务厅的灯牌就相当于线程池中协调线程和任务的池管理组件(互斥锁,条件变量,信号量)等等。

2、组成线程池的几部分
1、 就以上例子我们知道,要实现一个线程池要在程序开始时先创建一批线程,这一批创建好了的线程要然他们在没有任务可以执行的时候阻塞住,有一个任务需要执行的时候其中一个线程能够获取到任务,其他线程继续阻塞。获取到任务的线程在将任务函数执行完毕之后重新阻塞住等待下一个任务,直到线程池要被销毁的时候创建的所有线程被销毁。
2、 每当要执行一个任务的时候,该任务被加入到任务队列中去,同时通知阻塞等待任务的某个线程有任务要执行了,这时候收到有任务需要执行条件的线程从任务队列中拿走一个任务去执行。这里实际上是生产者消费者模型,生产者是将任务放进任务队列的线程,消费者是执行任务的线程,任务队列是交易场所。要维护好这个生产者消费者模型就必须用到互斥锁,条件变量,
信号量等等。这些就是线程池的管理组件。

C语言中实现线程池的数据结构设计:
1. 任务队列(要执行的任务函数的函数指针,函数的参数)
2. 执行队列(线程id, 线程终止标识, 池管理组件)
3. 池管理组件(互斥锁,条件变量, 任务队列, 执行队列)

线程池的代码实现:C版本

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <pthread.h>
//在队列中增加一个结点的宏函数  (头插)
#define LL_ADD(item, list) do {     \
    item->prev = NULL;              \
    item->next = list;              \
    list = item;                    \
} while(0)
//在队列中删除一个节点的宏函数
#define LL_REMOVE(item, list) do {                      \
    if (item->prev != NULL) item->prev->next = item->next;  \
    if (item->next != NULL) item->next->prev = item->prev;  \
    if (list == item) list = item->next;                    \
    item->prev = item->next = NULL;                         \
} while(0)

typedef struct NWORKER {//这里是执行队列,队列中的结点有执行任务的线程id,表示线程是否执行完毕任务的标志terminate
                        //指向池管理组件的指针,指向任务队列中前后结点的指针。
    pthread_t thread;
    int terminate;
    struct NWORKQUEUE *workqueue;
    struct NWORKER *prev;
    struct NWORKER *next;
} nWorker;

typedef struct NJOB {//这里是任务队列,队列中每个结点有执行任务的函数指针,执行任务需要的数据
                     //还有指向任务队列前后结点的指针。
    void (*job_function)(struct NJOB *job);
    void *user_data;
    struct NJOB *prev;
    struct NJOB *next;
} nJob;

typedef struct NWORKQUEUE {//池管理组件,有一个指向任务队列的指针和一个指向执行队列的指针
    struct NWORKER *workers;//还有管理线程池需要的互斥量和条件变量。
    struct NJOB *waiting_jobs;
    pthread_mutex_t jobs_mtx;
    pthread_cond_t jobs_cond;
} nWorkQueue;

typedef nWorkQueue nThreadPool;

static void *ntyWorkerThread(void *ptr) {//创建线程的线程入口函数
    nWorker *worker = (nWorker*)ptr;/输入的参数是执行队列的结点

    while (1) {
        pthread_mutex_lock(&worker->workqueue->jobs_mtx);//加锁

        while (worker->workqueue->waiting_jobs == NULL) {
            if (worker->terminate) break;//线程任务结束 不等待条件变量并释放锁
            pthread_cond_wait(&worker->workqueue->jobs_cond, &worker->workqueue->jobs_mtx);
        }//条件变量控制当有任务时 获取到锁   如果该线程没有从任务队列取到任务则阻塞在这里。

        if (worker->terminate) {
            pthread_mutex_unlock(&worker->workqueue->jobs_mtx);//解锁
            break;//如果线程需要结束了就跳出while循环 然后释放
        }

        nJob *job = worker->workqueue->waiting_jobs;w出任务队列中取出一个任务
        if (job != NULL) {//如果任务队列中有任务
            LL_REMOVE(job, worker->workqueue->waiting_jobs);//将任务队列中被取出放入执行队列的任务清除
        }

        pthread_mutex_unlock(&worker->workqueue->jobs_mtx);

        if (job == NULL) continue;

        job->job_function(job);//调用真正执行任务的函数。执行完毕后继续while循环等待新的任务
    }

    free(worker);
    pthread_exit(NULL);
}
//创建一个线程池            //池管理组件的指针       //初始化线程池中线程的数量
int ntyThreadPoolCreate(nThreadPool *workqueue, int numWorkers) {

    if (numWorkers < 1) numWorkers = 1;
    memset(workqueue, 0, ezeof(nThreadPool));

    pthread_cond_t blank_cond = PTHREAD_COND_INITIALIZER;//初始化池管理组件中的条件变量
    memcpy(&workqueue->jobs_cond, &blank_cond, sizeof(workqueue->jobs_cond));

    pthread_mutex_t blank_mutex = PTHREAD_MUTEX_INITIALIZER;//初始化池管理组件中的锁
    memcpy(&workqueue->jobs_mtx, &blank_mutex, sizeof(workqueue->jobs_mtx));

    int i = 0;
    for (i = 0;i < numWorkers;i ++) {//循环创建线程
        nWorker *worker = (nWorker*)malloc(sizeof(nWorker));//创建一个执行队列的结点
        if (worker == NULL) {
            perror("malloc");
            return 1;i
        }

        memset(worker, 0, sizeof(nWorker));
        worker->workqueue = workqueue;//执行队列结点指向池管理组件
        //创建一个以执行队列结点中的thread为线程id,执行队列结点的指针为线程入口函数的参数
        int ret = pthread_create(&worker->thread, NULL, ntyWorkerThread, (void *)worker);
        if (ret) {

            perror("pthread_create");
            free(worker);//创建线程失败的话,释放执行队列的结点
            return 1;
        }

        LL_ADD(worker, worker->workqueue->workers);//将执行任务的结点加入执行队列
    }
    return 0;
}
//关闭线程池
void ntyThreadPoolShutdown(nThreadPool *workqueue) {
    nWorker *worker = NULL;

e   for (worker = workqueue->workers;worker != NULL;worker = worker->next) {
        wiorker->terminate = 1;//将每个线程的状态设置为已退出。
    }

    pthread_mutex_lock(&workqueue->jobs_mtx);

    workqueue->workers = NULL;
    workqueue->waiting_jobs = NULL;

    pthread_cond_broadcast(&workqueue->jobs_cond);
    pthread_mutex_unlock(&workqueue->jobs_mtx);
}

void ntyThreadPoolQueue(nThreadPool *workqueue, nJob *job) {

    pthread_mutex_lock(&workqueue->jobs_mtx);

    LL_ADD(job, workqueue->waiting_jobs);//当有新的任务的时候 ,添加到任务队列中去
         w                               
    pthread_cond_signal(&workqueue->jobs_cond);x//告诉池控件已经有新的任务了
    pthread_mutex_unlock(&workqueue->jobs_mtx);

}

/************************** debug thread pool **************************/

#define KING_MAX_THREAD         80
#define KING_COUNTER_SIZE       1000

void king_counter(nJob *job) {//线程池中线程执行的任务

    int index = *(int*)job->user_data;

    printf("index : %d, selfid : %lu\n", index, pthread_self());//打印出线程处理函数的参数,并且打印出线程id

    free(job->user_data);
    free(job);
}

int main(int argc, char *argv[]) {

    nThreadPool pool;

    ntyThreadPoolCreate(&pool, KING_MAX_THREAD);//创建线程池

    int i = 0;
    for (i = 0;i < KING_COUNTER_SIZE;i ++) {
        nJob *job = (nJob*)malloc(sizeof(nJob));
        if (job == NULL) {
            perror("malloc");
            exit(1);
        }

        job->job_function = king_counter;
        job->user_data = malloc(sizeof(int));
        *(int*)job->user_data = i;//构造任务队列的任务

        ntyThreadPoolQueue(&pool, job);//向任务队列中插入一个任务并通过条件变量告诉执行队列有任务到来了
    }
    getchar();
    printf("\n");
}

C++语言中线程池数据结构的设计:
线程池被封装为一个类,类中的成员有vector 或 list类型的任务队列,创建出来的所以线程的id,所有线程的退出标志。池管理控件(信号量,互斥量,条件变量等)。线程入口函数的函数指针等。

线程池的实现 C++:
线程池控件文件: 封装互斥锁与信号量
locker.h

#ifndef __LOCKER_H__
#define __LOCKER_H__

#include<exception>
#include<pthread.h>
#include<semaphore.h>

class sem//信号量的类
{
public:
    sem();//创建信号量
    ~sem();//删除信号量
    bool wait();//信号量P操作   减一
    bool post();//信号量V操作   加一
private:
    sem_t m_sem;
};

class locker
{
public:
    locker();//创建锁
    ~locker();//释放锁
    bool lock();//加锁
    bool unlock();//解锁
private:
    pthread_mutex_t m_mutex;
};


class cond
{
public:
    cond();//创建条件变量
    ~cond();//释放条件变量
    bool wait();//等待条件变量
    bool signal();//通知条件变量就绪
private:
    pthread_mutex_t m_mutex;
    pthread_cond_t m_cond;
};

#endif

locker.cpp

#include"locker.h"

sem::sem()
{
    if(sem_init(&m_sem,0,0)!=0)  
    {
        throw std::exception();
    }
}

sem::~sem()
{
    sem_destroy(&m_sem);
}

bool sem::wait()
{
    return sem_wait(&m_sem)==0;
}

bool sem::post()
{
    return sem_post(&m_sem)==0;
}

locker::locker()
{
    if(pthread_mutex_init(&m_mutex,NULL)!=0)
    {
        throw std::exception();
    }
}

locker::~locker()
{
    pthread_mutex_destroy(&m_mutex);
}

bool locker::lock()
{
    return pthread_mutex_lock(&m_mutex);
}

bool locker::unlock()
{
    return pthread_mutex_unlock(&m_mutex);
}

cond::cond()
{
    if(pthread_mutex_init(&m_mutex,NULL)!=0)
    {
        throw std::exception();
    }

    if(pthread_cond_init(&m_cond,NULL)!=0)
    {
        throw std::exception();
    }
}

cond::~cond()
{
    pthread_mutex_destroy(&m_mutex);
    pthread_cond_destroy(&m_cond);
}

bool cond::wait()
{
    int ret=0;
    pthread_mutex_lock(&m_mutex);
    ret=pthread_cond_wait(&m_cond,&m_mutex);     //等待条件满足在执行下一步
    pthread_mutex_unlock(&m_mutex);
    return ret==0;
}

bool cond::signal()
{
    return pthread_cond_signal(&m_cond)==0;
}

线程池:

#include<iostream>
#include<pthread.h>
#include<cstdio>
#include<list>
#include<exception>

#include"locker.h"

//为了提高复用性,我们用模板实现线程池,模板类型T是任务类
template<typename T>
class threadpool
{
public:
    threadpool(int thread_number=8,int max_requests=1000);
    ~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;           //指向管理线程tid的数组
    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_stop(false)
     ,m_threads(NULL)
{
    if((thread_number<=0)||(max_requests<=0))
    {
        throw std::exception();
    }

    m_threads=new pthread_t[thread_number];  //定义线程id数组
    if(!m_threads)
    {
        throw std::exception();
    }

    for(int i=0;i<thread_number;i++)          //创建thread_number个线程,并且将其设置为分离状态
    {
        if(pthread_create(m_threads+i,NULL,worker,(void*)this)!=0)
        {//因为threadpool类中有请求队列,池的控件(锁,信号量等),还有执行队列(线程tid数组)
            //所以这里作为创建线程时线程入口函数的参数
            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();                         //每添加一个任务,信号量增加 通知
    //这里相当于条件变量singal() 通知执行队列有任务可以取到
    return true;
}

template<typename T>
void* threadpool<T>::worker(void *arg)
{
    threadpool *pool=(threadpool*)arg;
    pool->run();                        //调用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();
        request->process();      //process是任务类里面的一个方法 这是线程真正在执行的任务。
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值