linux进程间通信---本地socket套接字(六)---线程池原理及C语言实现线程池

先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
在这里插入图片描述
**

一 why

**
先谈谈为什么会有这篇博客,如果只是为了介绍线程池,我相信大家都有相关概念,或者去各大技术博客上也能搜到相关知识。那在这里我为什么还要谈谈线程池的知识呢?

在前面的博客《linux进程间通信—本地socket套接字(三)—多线程实现一个server对应多个client》中,我们处理多个client socket连接通信的思路是:

  1. 先创建一个主线程,用于检测客户端的连接事件
  2. 每当server检测到一个client连接时,则创建一个消息通信处理线程,该线程只和这个client进行数据通信处理;实际上这是一种多线程并发服务器的实现方案。
    在这里插入图片描述
    但是采用多线程并发服务器的处理方式有什么缺点呢?考虑如下几个问题:
    (1) 每次连接的时候,server就需要创建一个数据处理的线程,创建的过程也是需要时间消耗的,当客户端数据传输较快,或者客户端数量较多时,就会影响客户端的处理速度;
    (2) 每当一个client退出连接时,我们需要销毁之前创建的数据通信线程,显然销毁的过程也需要时间消耗,也会影响性能速度;

对大多数小型局域网的通信来说,上述方法足够满足需求;但当我们的通信范围扩大到广域网或大型局域网通信中时,我们将面临大量消息频繁请求服务器;在这种情况下,创建与销毁线程都已经成为一种奢侈的开销,特别对于嵌入式服务器来说更应保证内存资源的合理利用;
因此,线程池技术应运而生。它允许一个线程可以多次复用,且每次复用的线程内部的消息处理可以不相同,将创建与销毁的开销省去而不必来一个请求开一个线程;
(1) server端先创建一堆线程池,这个时候因为没有客户端连接,所有线程池都处于阻塞等待状态
(2) 每当检测到一个client连接时,在线程池中找出一个线程去处理这个客户端,当这个客户端退出时,断开线程和客户端的连接关系,暂时不销毁线程
(3) 如果初始创建的线程数量不够,我们需要新增线程;如果由于新增了多个线程之后,由于client的退出,我们目前不需要这么多线程,就需要删除多余的线程。
**

二 what

**
上面介绍了多线程实现socket sever端的方案,本博客我们先不介绍具体的实现方案,我们先来熟悉一下如何创建一个线程池,并使得它正常运行。
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务;它是一个抽象的概念,其内部由任务队列,一堆线程,管理者线程组成。
在这里插入图片描述
**

三 how

**
我们以上图为例,分别介绍:1.线程池结构;2.线程数组;3.任务队列;4.管理者线程
(1) 线程池结构

typedef struct {
    void *(*function)(void *);  /*函数指针,回调函数*/
    void *arg;                  /*上面函数的参数*/
} threadpool_task_t;            /*各子线程任务结构体*/

typedef struct threadpool_s {
    pthread_mutex_t lock;            /*用于锁住本结构体*/
    pthread_mutex_t thread_counter;  /*记录忙状态线程个数的锁 -- busy_thr_num*/
    pthread_cond_t queue_not_full;   /*当队列任务满时,添加任务的线程阻塞,等待此条件变量*/
    pthread_cond_t queue_not_empty;  /*任务队列不为空时,通知等待任务的线程*/

    pthread_t *threads;              /*存放线程池中每个线程的tid,数组*/
    pthread_t adjust_tid;            /*存管理线程tid*/
    threadpool_task_t *task_queue;   /*任务队列*/

    int min_thr_num;                 /*线程池最小线程数*/
    int max_thr_num;                /*线程池最大线程数*/
    int live_thr_num;                /*当前存活线程个数*/
    int busy_thr_num;                /*忙线程个数*/
    int wait_exit_thr_num;           /*要销毁的线程个数*/

    int queue_front;                 /*task_queue队头下表*/
    int queue_rear;                  /*task_queue队尾下表*/
    int queue_size;                  /*task_queue队列中实际任务数*/
    int queue_max_size;              /*task_queue队列可容纳任务上限*/

    int shutdown;                    /*标志位,线程池使用状态,true或者false*/
} threadpool_t;
创建线程池
threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size)
{
    int i;
    threadpool_t *pool = NULL;
    
    do {
        pool = (threadpool_t *)malloc(sizeof(threadpool_t));
        if (pool == NULL) {
            printf("malloc threadpool fail\n");
            break;
        }
        
        pool->min_thr_num = min_thr_num;
        pool->max_thr_num = max_thr_num;
        pool->busy_thr_num = 0;
        pool->live_thr_num = min_thr_num;
        pool->queue_size = 0;
        pool->queue_max_size = queue_max_size;
        pool->queue_front = 0;
        pool->queue_rear = 0;
        pool->shutdown = 0;
        
        /*根据最大线程上限数,给工作线程数据开辟空间,并清零*/
        pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * max_thr_num);
        if (pool->threads == NULL) {
            printf("malloc threads fail\n");
            break;
        }
        memset(pool->threads, 0, sizeof(pthread_t) * max_thr_num);
        
        /* 队列开辟空间 */
        pool->task_queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t) * queue_max_size);
        if (pool->task_queue == NULL) {
            printf("malloc task_queue fail\n");
            break;
        }
        
        /* 初始化互斥锁,条件变量 */
        if (pthread_mutex_init(&(pool->lock), NULL) != 0
            || pthread_mutex_init(&(pool->thread_counter), NULL) != 0
            || pthread_cond_init(&(pool->queue_not_empty), NULL) != 0
            || pthread_cond_init(&(pool->queue_not_full), NULL) != 0) {
                printf("init the lock or cond fail\n");
                break;
            }
        
        /* 启动 min_thr_num 个 work thread */
        for (i = 0; i < min_thr_num; i++) {
            pthread_create(&(pool->threads[i]), NULL, threadpool_thread,
                (void *)pool); /*pool指向当前线程池*/
            printf("start thread  0x%x...\n", (unsigned int)pool->threads[i]);
        }
        pthread_create(&(pool->adjust_tid), NULL, adjust_thread, (void *)pool);
        
        return pool;
    } while(0);
    
    threadpool_free(pool);    /*前面代码调用失败,释放poll存储空间*/
    return NULL;
}

(2) 线程数组
线程数组实际上是在线程池初始化时开辟的一段存放一堆线程tid的空间,在逻辑上形成一个池,里面放置着提前创建的线程;这段空间中包含了正在工作的线程,等待工作的线程(空闲线程),等待被销毁的线程,申明但没有初始化的线程空间;
在这里插入图片描述

工作线程

void *threadpool_thread(void *threadpool)
{
    threadpool_t *pool = (threadpool_t *)threadpool;
    threadpool_task_t task;

    while(1) {
        /* Lock must be taken to wait on condition variable */
        /* 刚创建出线程,等待任务队列里面有任务,否则阻塞等待任务队列里有任务再唤醒
         * 接收任务
         */
        pthread_mutex_lock(&(pool->lock));
        
        /* queue_size == 0 说明没有任务,调wait 阻塞在条件变量上,若有任务,跳过该while */
        while((pool->queue_size == 0) && (!pool->shutdown)) {
            printf("thread 0x%x is waiting\n", (unsigned int)pthread_self());
            pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
            
            /* 清除指定数目的空闲线程,如果要结束的线程个数大于0,结束线程 */
            if (pool->wait_exit_thr_num > 0) {
                pool->wait_exit_thr_num--;
                
                /* 如果线程池里的线程个数大于最小值时可以结束当前线程 */
                if (pool->live_thr_num > pool->min_thr_num) {
                    printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
                    pool->live_thr_num--;
                    pthread_mutex_unlock(&(pool->lock));
                    pthread_exit(NULL);
                }
            }
        }
        
        /*如果关闭了线程池,自行退出处理*/
        if (pool->shutdown == 1) {
            pthread_mutex_unlock(&(pool->lock));
            printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
            pthread_exit(NULL);
        }
        
        /*从任务队列里获取任务,是一个出队操作*/
        task.function = pool->task_queue[pool->queue_front].function;
        task.arg = pool->task_queue[pool->queue_front].arg;

        /*出队,模拟环形队列*/
        pool->queue_front = (pool->queue_front + 1) % pool->queue_max_size;
        pool->queue_size--;
        
        /*通知可以有新的任务添加进来*/
        pthread_cond_broadcast(&(pool->queue_not_full));
        
        /*任务取出后,立即将线程池锁释放*/
        pthread_mutex_unlock(&(pool->lock));
        
        /*执行任务*/
        printf("thread 0x%x starts woking\n", (unsigned int)pthread_self());
        pthread_mutex_lock(&(pool->thread_counter));    /*忙状态线程数变量锁*/
        pool->busy_thr_num++;                           /*忙状态线程数+1*/
        pthread_mutex_unlock(&(pool->thread_counter));
        (*(task.function))(task.arg);                   /*执行回调函数*/
        
        /*任务结束处理*/
        printf("thread 0x%x ends woking\n", (unsigned int)pthread_self());
        pthread_mutex_lock(&(pool->thread_counter));
        pool->busy_thr_num--;
        pthread_mutex_unlock(&(pool->thread_counter));
    }
    
    return NULL;
}

(3) 任务队列
任务队列的存在形式与线程数组相似;在线程池初始化时根据传入的最大任务数开辟空间;当服务器前方请求到来后,分类并打包消息成为任务,将任务放入任务队列并通知空闲线程来取;不同之处在于任务队列有明显的先后顺序,先进先出;而线程数组中的线程则是一个竞争关系去拿到互斥锁争取任务;
在这里插入图片描述

/*向线程池的任务队列中添加一个任务*/
int threadpool_add(threadpool_t *pool, void *function(void *arg), void *arg)
{
    pthread_mutex_lock(&(pool->lock));
    
    /*为真,队列已满,调wait等待*/
    while ((pool->queue_size == pool->queue_max_size) && (!pool->shutdown)) {
        pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
    }
    
    if (pool->shutdown) {
        pthread_mutex_unlock(&(pool->lock));
    }
    
    /*清空工作线程 调用回调函数的参数 arg*/
    if (pool->task_queue[pool->queue_rear].arg != NULL) {
        free(pool->task_queue[pool->queue_rear].arg);
        pool->task_queue[pool->queue_rear].arg = NULL;
    }
    /*添加任务到任务队列里面*/
    pool->task_queue[pool->queue_rear].function = function;
    pool->task_queue[pool->queue_rear].arg = arg;
    pool->queue_rear = (pool->queue_rear + 1) % pool->queue_max_size;
    pool->queue_size++;
    
    /*添加完任务后,队列不为空,唤醒线程池中等待处理任务的线程*/
    pthread_cond_signal(&(pool->queue_not_empty));
    pthread_mutex_unlock(&(pool->lock));
    
    return 0;
}

(4) 管理者线程
作为线程池的管理者,该线程的主要功能包括:检查线程池内线程的存活状态,工作状态;负责根据服务器当前的请求状态去动态的增加或删除线程,保证线程池中的线程数量维持在一个合理高效的平衡上;
说到底,它就是一个单独的线程,定时的去检查,根据我们的一个维持平衡算法去增删线程;

void *adjust_thread(void *threadpool)
{
    threadpool_t *pool = (threadpool_t *)threadpool;
    int i;
    
    while (!pool->shutdown) {
        sleep(DEFAULT_TIME);   /*定时对线程池管理*/
        
        pthread_mutex_lock(&(pool->lock));
        int queue_size = pool->queue_size;
        int live_thr_num = pool->live_thr_num;
        pthread_mutex_unlock(&(pool->lock));
        
        pthread_mutex_lock(&(pool->thread_counter));
        int busy_thr_num = pool->busy_thr_num;
        pthread_mutex_unlock(&(pool->thread_counter));
        
        /* 创建新线程算法,任务数大于最小线程池个数,
         * 且存活的线程数小于最大线程数时
         */
        if (queue_size >= MIN_WAIT_TASK_NUM && live_thr_num < pool->max_thr_num){
            pthread_mutex_lock(&(pool->lock));
            int add = 0;
            
            /* 一次增加 DEFAULT_THREAD_VERY 个线程*/
            for (i = 0; i < pool->max_thr_num && add < DEFAULT_THREAD_VERY
                    && pool->live_thr_num < pool->max_thr_num; i++) {
                if (pool->threads[i] == 0 || is_thread_alive(pool->threads[i])) {
                    pthread_create(&(pool->threads[i]), NULL, threadpool_thread,
                        (void *)pool);
                    add++;
                    pool->live_thr_num++;
                }
            }
            
            pthread_mutex_unlock(&(pool->lock));
        }
        
        /* 销毁多余的空闲线程算法,忙线程 x2 小于存活的线程数 且 
         * 存活的线程数大于最小线程数时
         */
        if (busy_thr_num * 2 < live_thr_num && live_thr_num > pool->min_thr_num) {
            /*一次销毁 DEFAULT_THREAD_VERY 个线程*/
            pthread_mutex_lock(&(pool->lock));
            pool->wait_exit_thr_num = DEFAULT_THREAD_VERY;
            pthread_mutex_unlock(&(pool->lock));
            
            for (i = 0; i < DEFAULT_THREAD_VERY; i++) {
                /*通知处在空闲状态的线程,他们会自行终止,线程自杀*/
                pthread_cond_signal(&(pool->queue_not_empty));
            }
        }
    }
    
    return NULL;
}

(5) 其他接口

int threadpool_distory(threadpool_t *pool)
{
    int i;
    if (pool == NULL) {
        return -1;
    }

    pool->shutdown = 1;
    
    /*先销毁管理线程*/
    pthread_join(pool->adjust_tid, NULL);
    
    for (i = 0; i < pool->live_thr_num; i++) {
        /*通知所有空闲线程*/
        pthread_cond_broadcast(&(pool->queue_not_empty));
    }
    for (i = 0; i < pool->live_thr_num; i++) {
        pthread_join(pool->threads[i], NULL);
    }
    threadpool_free(pool);
    
    return 0;
}

int threadpool_free(threadpool_t *pool)
{
    if (pool == NULL) {
        printf("thread pool is already free\n");
        return -1;
    }
    
    if (pool->task_queue) {
        free(pool->task_queue);
    }
    
    if (pool->threads) {
        free(pool->threads);
        pthread_mutex_lock(&(pool->lock));
        pthread_mutex_destroy(&(pool->lock));
        pthread_mutex_lock(&(pool->thread_counter));
        pthread_mutex_destroy(&(pool->thread_counter));
        pthread_cond_destroy(&(pool->queue_not_empty));
        pthread_cond_destroy(&(pool->queue_not_full));
    }
    free(pool);
    pool = NULL;
    
    return 0;
}

int is_thread_alive(pthread_t tid)
{
    int kill_rc = pthread_kill(tid, 0); /*发0号信号,测试线程是否存活*/
    if (kill_rc == ESRCH) {
        return 0;
    }
    
    return 1;
}

**
(6) 测试用例

#if 1
/* 线程池中的线程,模拟处理业务 */
void process(void *arg)
{
    printf("thread 0x%x working on task %d\n", (unsigned int)pthread_self(),
        *(int *)arg);
    sleep(1);
    printf("task %d is end\n", *(int *)arg);
}

int main(int argc, char **argv)
{
    int num[20], i;
    /*threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size)*/
    
    /* 创建线程池,池里最小3个线程,最大100,队列最大100 */
    threadpool_t *thp = threadpool_create(3, 100, 100);
    if (thp == NULL) {
        printf("threadpool_create fail\n");
        return 0;
    }
    printf("pool init\n");
    
    for (i = 0; i < 20; i++) {
        num[i] = i;
        printf("add task %d\n", i);
        /* 向线程池中添加任务 */
        threadpool_add(thp, (void *)&process, (void *)&num[i]);
    }
    /*等待子线程完成任务*/
    sleep(20);
    threadpool_distory(thp);
    
    return 0;
}
#endif

四 test

**
编译并运行

gcc server.c -o server -lpthread
./server
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值