深入剖析池式基础组件---------线程池(手撕线程池代码)

目录:

1.为什么会有线程池?

2.线程池是什么?

3.如何实现线程池?

引言:

线程池在后端服务器开发中是一个非常重要的基础组件,可以显著性地提升程序性能。在面试中,也会经常被面试官问到。线程池本身是一个生产者---消费者模型结构。实际上,针对于不同的业务场景,实现的方式会有多种,但是线程池本身的代码结构与设计思想是一样的,对于我们而言,弄懂线程池的代码结构是最重要的。本文,以C语言实现并讲解一个功能全面的是线程池的代码,以帮助大家深入理解线程池。

1 为什么会有线程池:

考虑这样的一个问题,一个线程在执行任务的时候,发现某一个步任务比较耗时,影响到了整个线程的运行时间,这个时候,我们该怎么处理这个问题呢?一个简单的回答是,我们可以将该步的任务放入一个子线程中,使得主线程与该任务解耦。比如,以一个服务器的网络收发为例:一个网络服务器的模型通常是一个while()死循环,不断地接受客户端的连接,可以分为4个步奏: accept,read,deal,send,deal的时候,可能是最耗时的操作。那么,我们可以将该步放入一个新的线程中。然而,客户端的连接是不定时的,也是不确定的;另外,一个线程的处理无法满足多个对于客户端请求的响应。如果一个客户端连接的请求分配一个线程,那么,随着客户端的响应,线程会不断地创建与销毁,带来了线程创建与销毁的资源开销。如果,我们可以创建一定数量的线程,并让其按照一定的顺序和规则,不断复用,那么,这些问题就解决了。这种方法被称为线程池技术。

对于一个线程池而言,比较重要的是,我们该分配多少个线程。线程数,处理的任务量与CPU核心数量,三者应该达到平衡。一个经验公式是:对于计算机本身而言,耗时任务分为两类,io密集型,cpu计算耗时型,对于cpu耗时型,一般线程数为cpu核心的数量,对于io密集型,为2*(n+1),n为cpu的数量。

 2. 线程池是什么?

经过上面的介绍,我们可以知道,一个线程池的组成是: 分布任务的线程,处理任务的线程,以及把两者解耦的任务队列;发布任务的线程把任务放入任务队列中,处理任务的线程从任务队列取走任务;至于,发布任务的线程有多少个,取决于具体的业务场景,总的规则是与处理任务的线程平衡。具体而言,分布任务的线程就是生产者,处理任务的线程就是消费者,任务队列中的一个任务需要包含任务的上下文与处理函数;本文讲解的线程池是单生产者多消费者模型,也就是一个发布任务的线程,多个处理任务的线程,在多个处理任务的线程中,重要的是,线程调度的实现与负责均衡;本文的代码以最简单的mutex锁与condition条件变量作为线程调度的机制。

3.

如何现实线程池?

接口设计:1.创建线程池:包括消费线程的数量,以及任务队列的长度;2.销毁线程池:通知所有线程退出,停止生产者放入任务;3.任务队列的收取接口:构造任务,放入队列,通知线程池中的任务。大家可以想想为什么这些接口这样设计。

4.手撕线程池代码:

在前面做好了线程池的概念讲解以后,接下来,我们将手撕线程池的代码,实现一个单生产者多消费者的线程池模型。

结构体的定义:


#include <pthread.h>  

typedef struct thread_pool_t thread_pool_t;
typedef void (*handler_pt) (void *);

typedef struct task_s{
   handler-pt task_func; //队列处理函数
   void* arg;      //函数的参数,可以封装一个结构体,以实现多个参数传递
}task_t;

typedef struct task_queue_s{
    uint32_t head;    //头指针
    uint32_t tail;    //尾指针
    uint32_t count;   //当前任务的数量
    task_t *queue;    //储存任务的队列
}task_queue_t;

typedef struct thread_pool_s{
    pthread_mutex_t mutex;
    pthread_cond_t condition;
    pthread_t *threads;//线程数组
    task_queue_t task_queue;//任务队列

    bool thread_poll_status ;//0为关闭,1为开启

    int thrd_count; //线程的数量
    int queue_size;  //任务的大小
}thread_pool_t;

线程池的创建:

//线程池的创建
thread_pool_t* thread_pool_create(int thread_count, int task_queue_count){
  if(thread_count<=0 || task_queue_task<=0){
    std::cout<<"thread_pool_create fails"<<std::endl;
    return nullptr;
  }

  thread_poll_t* pool=(thread_poll_t*)malloc(sizeof(thread_pool_t));
  if(pool==nullptr){
    std::cout<<"pool malloc fails"<<std::endl;
    return nullptr;
  }
  memset(pool,0,sizeof(thread_pool_t));
 
  pool->task_queue.head=pool->task_queue.tail==pool->task_queue.count=0;

  pool->task_queue.queue=(task_t*)malloc(sizeof(task_t)*queue_size);
  if (pool->task_queue.queue == nullptr) {
         std::cout<<"pool tasks malloc fails"<<std::endl;
         free(poll);
        return nullptr;
  }
  memset(pool->task_queue.queue,0,sizeof(task_t)*queue_size);

 pool->threads=(pthread_t*)malloc(sizeof(pthread_t)*thread_count);
 if(pool->threads==nullptr){
    std::cout<<"pool threads malloc fails"<<std::endl;
    free(poll);
    free(pool->task_queue.queue);
    return nullptr;
  }
  
 for (int i=0; i < thrd_count; i++) {
     if (pthread_create(&(pool->threads[i]), NULL, thread_worker, void* p) != 0) {
        std::cout<<"pool threads create fails"<<std::endl;
        free(poll);
        free(pool->task_queue.queue);
        return nullptr;
        }

    pool->thread_count++;
  }

  pool->thread_poll_status=true;
  pool->task_queue_size=queue_size;

  return pool; 
}

给线程池中的任务队列增加一个任务,需要加锁,同时加入条件变量唤醒线程。


int thread_pool_post(thread_pool_t *pool, handler_pt func, void *arg) {

   if(pool==nullptr || func==nullptr || arg==nullptr){
     std::cout<<" input arguments wrong"<<std::endl;
     return -1;   //这里可以加错误处理函数
   }
  
   task_queue_t *task_queue = &(pool->task_queue);
   if(task_queue==nullptr){
    std::cout<<" pool task_queue wrong"<<std::endl;
    return -2;   //这里可以加错误处理函数
   }

   int ret=0;

  //给队列加锁,使用自旋锁会更优

   ret=pthread_mutex_lock(&(pool->mutex));
   if(ret!=0){
    std::cout<<" mutex lock wrong"<<std::endl;
    return -3;   //加锁失败
   }
   
   if (pool->thread_poll_status ==false) {
        pthread_mutex_unlock(&(pool->mutex));
        std::cout<<" thread_poll status wrong"<<std::endl;
        return -3;//线程池未开启
    }
   
     if (task_queue->count == pool->queue_size) {
        pthread_mutex_unlock(&(pool->mutex));
        std::cout<<" pool queue is full"<<std::endl;
        return -4; //任务队列已满
    }
   
    task_queue->queue[task_queue->tail].func = func;
    task_queue->queue[task_queue->tail].arg = arg;
    task_queue->tail =( task_queue->tail+1) % pool->queue_size;//这里是取余操作
    task_queue->count++;

    ret=pthread_cond_signal(&(pool->condition));
    if(ret!=0){
        pthread_mutex_unlock(&(pool->mutex));
        std::cout<<" cond_signal wrong"<<std::endl;
        return -5;
    }

    pthread_mutex_unlock(&(pool->mutex));
    return 0;
}

线程池中线程的职责:1.取出任务  2.执行任务 3. 线程调度

void * thread_worker(void *thrd_pool) {

    thread_pool_t *pool = (thread_pool_t*)thrd_pool;//线程池入口函数
    task_queue_t *que;
    task_t task;

    while(1) {

        pthread_mutex_lock(&(pool->mutex)); //加锁
        que = &pool->task_queue; //取出任务

        while (que->count == 0 && pool->thread_pool_status ==false) { 
            pthread_cond_wait(&(pool->condition), &(pool->mutex));//阻塞线程
           //加while()循环是防止系统本身虚假唤醒线程,或者业务逻辑不严谨
        }
        
      //使用条件变量进行线程调度
        if (pool->thread_poll_status==false) break;

        task = que->queue[que->head];
        que->head = (que->head + 1) % pool->queue_size; //循环队列
        que->count--;
        pthread_mutex_unlock(&(pool->mutex));//解锁

        (*(task.func))(task.arg); //执行任务
    }

    pthread_mutex_unlock(&(pool->mutex));//解锁
    pthread_exit(NULL); //退出线程
 

线程池的销毁:  1.等待所有的线程安全退出  2.释放已经申请的资源,包括锁,条件变量,内存资源。

1.等待所有线程安全退出:

int wait_all_done(thread_pool_t *pool) {
    int i, ret=0;
    for (i=0; i < pool->thrd_count; i++) {
        if (pthread_join(pool->threads[i], NULL) != 0) {
            ret=1;
        }
    }   
    return ret; //成功返回1,失败返回0
}

2. 释放资源:

 void thread_pool_free(thread_pool_t *pool) {
    if (pool == NULL) {
        return;
    }

    if (pool->threads) {
        free(pool->threads);
        pool->threads = NULL;

        pthread_mutex_lock(&(pool->mutex));
        pthread_mutex_destroy(&pool->mutex);
        pthread_cond_destroy(&pool->condition);
    }

    if (pool->task_queue.queue) {
        free(pool->task_queue.queue);
        pool->task_queue.queue = NULL;
    }
    free(pool);
}

3.销毁线程池的函数


int thread_pool_destroy(thread_pool_t *pool) {
    if (pool == NULL) {
        return -1;
    }

    if (pthread_mutex_lock(&(pool->mutex)) != 0) {
        return -2;
    }

    if (pool->thread_pool_status==false) {
        thread_pool_free(pool);
        return -3;
    }

   //通过广播信号唤醒所有等待在条件变量pool->condition上的线程,并让它们重新竞争
    if (pthread_cond_broadcast(&(pool->condition)) != 0 ||  pthread_mutex_unlock(&(pool->mutex)) != 0) {
        thread_pool_free(pool);
        return -4;
    }

    wait_all_done(pool);

    thread_pool_free(pool);
    pool->thread_pool_status=false;
    return 0;
}

总结:

总的来说,线程池包括一个任务队列(一般是循环队列),放入任务的线程(生产者),处理任务的线程(消费者);主要的技术是通过mutex 和condition实现临界资源的互斥访问与线程的等待与通知。具体使用要结合业务场景进行变化,变生产者,任务队列,消费者中的其一或者其二,以实现性能的最好。

-----------------------------------------------------------------------------------------------------------------------end

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值