线程池基础知识与代码走读

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

 

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

 

函数pthread_cond_wait()用法分析:
1:条件变量是利用线程间共享的全局变量(例如:线程池的任务队列)进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
 

2:条件变量和互斥锁一样,都有静态动态两种创建方式(条件变量的初始化)。

静态方式使用PTHREAD_COND_INITIALIZER常量,如下:   

  1. pthread_cond_t   cond=PTHREAD_COND_INITIALIZER   

  

  1. 动态方式调用pthread_cond_init()函数,函数定义如下:   

 int   pthread_cond_init(pthread_cond_t   *cond,   pthread_condattr_t   *cond_attr)   

这里ond_attr值通常为NULL,且被忽略。

 

3:条件变量挂起,阻塞等待(有两种方式)

(1): int   pthread_cond_wait(pthread_cond_t   *cond,   pthread_mutex_t   *mutex) 

(2): int   pthread_cond_timedwait(pthread_cond_t   *cond,   pthread_mutex_t   *mutex,   const   struct   timespec   *abstime)

 

等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。 

  

无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。 

  

4:函数pthread_cond_signal() 或者 pthread_cond_broadcast()唤醒上诉的线程等待

激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。 

 

函数:pthread_join 

创建一个线程默认的状态是joinable, 如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收(退出状态码),所以创建线程者应该 pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于wait,waitpid) 。

 

调用pthread_join的线程会阻塞,直到指定的线程返回,调用了pthread_exit,或者被取消。如果线程简单的返回,那么rval_ptr被设置成线程的返回值,参见范例1;如果调用了pthread_exit,则可将一个无类型指针返回,在pthread_join中对其进行访问,参见范例2;如果线程被取消,rval_ptr被设置成PTHREAD_CANCELED。 如果我们不关心线程的返回值,那么我们可以把rval_ptr设置为NULL。

 

 

函数:pthread_detach

创建一个线程默认的状态是joinable, 如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收(退出状态码),所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于wait,waitpid)

但是调用pthread_join(pthread_id)后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此,比如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),这时可以在子线程中加入代码

pthread_detach(pthread_self())

或者父线程调用

pthread_detach(thread_id)(非阻塞,可立即返回)

这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。

 

 

 

函数:互斥锁 pthread_mutex_init()函数

1:函数原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

pthread_mutex_init()函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为NULL,则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。

2: 锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。

int pthread_mutex_lock(pthread_mutex_t *mutex)

int pthread_mutex_unlock(pthread_mutex_t *mutex)

int pthread_mutex_trylock(pthread_mutex_t *mutex)

3: 死锁:

 

死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的东西。

总体来讲, 有几个不成文的基本原则:

  对共享资源操作前一定要获得锁。

  完成操作以后一定要释放锁。

  尽量短时间地占用锁。

  如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。

  线程错误返回时应该释放它所获得的锁。

#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include "threadpool.h"

#define DEFAULT_TIME 10                 /*   */
#define MIN_WAIT_TASK_NUM 10            /**/ 
#define DEFAULT_THREAD_VARY 10          /**/
#define true 1
#define false 0

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

/* 描述线程池相关信息 */
struct threadpool_t {
    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;                       /* 标志位,线程池使用状态, ture(线程池关闭)或者false */
};

/**
 * @function void *threadpool_thread(void *threadpool)
 * @desc the worker thread
 * @param threadpool the pool which own the thread
 */
void *threadpool_thread(void *threadpool);

/**
 * @function void *adjust_thread(void *threadpool);
 * @desc manager thread
 * @param threadpool the threadpool
 */
void *adjust_thread(void *threadpool);

/**
 * check a thread is alive
 */
int is_thread_alive(pthread_t tid);
int threadpool_free(threadpool_t *pool);


//创建线程池 
threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size) // 3个参数,一般任务队列和线程最大数我们默认一样 
{
    int i;
    threadpool_t *pool = NULL;  //初始化定义一个线程池的局部变量 
    do {
        if((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL) {  //malloc开辟内存空间 
            printf("malloc threadpool fail");
            break;   /*跳出 do while*/
        }
	
		// 对pool初始化 
        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;                           /* 有0个产品 */
        pool->queue_max_size = queue_max_size;
        pool->queue_front = 0;
        pool->queue_rear = 0;
        pool->shutdown = false;                         /* 不关闭线程池 */

        /* 根据最大线程上限数,给工作线程数组开辟内存空间,并memset清零 */
        pool->threads = (pthread_t *)malloc(sizeof(pthread_t)*max_thr_num); 
        if (pool->threads == NULL) {
            printf("malloc threads fail");
            break;
        }
        memset(pool->threads, 0, sizeof(pthread_t)*max_thr_num);//对 pool->threads memset清0 

        /* 给任务队列开辟空间 */
        pool->task_queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t)*queue_max_size);
        if (pool->task_queue == NULL) {
            printf("malloc task_queue fail");
            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");
            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;
}

/*向线程池中添加一个任务*/
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_full), &(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)); //signal为唤醒一个线程去干活(就会去干threadpool_thread中的事

    return 0;
}

/* 工作线程主函数 */
void *threadpool_thread(void *threadpool)
{
    threadpool_t *pool = (threadpool_t *)threadpool; //把传入的参数赋值给局部变量 
    threadpool_task_t task;

    while (true) {
        /* Lock must be taken to wait on conditional 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);
                }
            }
        }

        /* 如果pool->shutdown指定了ture, 要关闭线程池里的每个线程,自行退出  */
        if (pool->shutdown) {
            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; // function赋值
        task.arg = pool->task_queue[pool->queue_front].arg; //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 start working\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);                                           /*解锁*/
        //task.function(task.arg);                                              /*执行回调函数任务*/

        /*任务结束处理*/ 
        printf("thread 0x%x end working\n", (unsigned int)pthread_self());
        pthread_mutex_lock(&(pool->thread_counter));
        pool->busy_thr_num--;                                       /*处理掉一个任务,忙状态线程数-1*/
        pthread_mutex_unlock(&(pool->thread_counter));
    }

    pthread_exit(NULL);
}

/* 管理线程(砍人加人) */
void *adjust_thread(void *threadpool)
{
    int i;
    threadpool_t *pool = (threadpool_t *)threadpool;
    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));

        /* 砍人加人的算法规则:任务数大于最小线程池个数,且存活的线程数少于最大线程个数时, 30>=10 && 40<100 */
        if (queue_size >= MIN_WAIT_TASK_NUM && live_thr_num < pool->max_thr_num) {
            pthread_mutex_lock(&(pool->lock));  
            int add = 0;

            /* 一次性添加DEFAULT_THREAD 个线程 */
            for (i = 0; i < pool->max_thr_num && add < DEFAULT_THREAD_VARY
                    && 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));
        }

        /* 销毁多余的空闲线程算法: 忙线程*2小于存活的线程数 且存活的线程数大于最小的线程数 */
        if ((busy_thr_num * 2) < live_thr_num  &&  live_thr_num > pool->min_thr_num) {

            /* 一次销毁DEFAULT_THREAD_VARY个线程 随机10个即可 */
            pthread_mutex_lock(&(pool->lock));
            pool->wait_exit_thr_num = DEFAULT_THREAD_VARY;      /* 每次要销毁的线程数(每次销毁步长) 设置为10  */
            pthread_mutex_unlock(&(pool->lock));

            for (i = 0; i < DEFAULT_THREAD_VARY; i++) {
                /* 通知处于空闲状态的线程,唤醒他们之后会进到 threadpool_thread 他们会自行终止 */
                pthread_cond_signal(&(pool->queue_not_empty)); // 让这个线程自杀
            }
        }
    }

    return NULL;
}

int threadpool_destroy(threadpool_t *pool)
{
    int i;
    if (pool == NULL) {
        return -1;
    }
    pool->shutdown = true;

    /*先销毁管理线程*/
    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) {
        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 threadpool_all_threadnum(threadpool_t *pool)
{
    int all_threadnum = -1;
    pthread_mutex_lock(&(pool->lock));
    all_threadnum = pool->live_thr_num;
    pthread_mutex_unlock(&(pool->lock));
    return all_threadnum;
}

int threadpool_busy_threadnum(threadpool_t *pool)
{
    int busy_threadnum = -1;
    pthread_mutex_lock(&(pool->thread_counter));
    busy_threadnum = pool->busy_thr_num;
    pthread_mutex_unlock(&(pool->thread_counter));
    return busy_threadnum;
}

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

    return true;
}

/*测试程序*/ 

#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);

    return NULL;
}
int main(void)
{
    /*threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size);*/

    threadpool_t *thp = threadpool_create(3,100,100);/* 创建线程池,池里最小3个线程 最大100, 队列最大100 */
    printf("pool inited");

    //int *num = (int *)malloc(sizeof(int)*20);
    int num[20], i;
    for (i = 0; i < 20; i++) {
        num[i]=i;
        printf("add task %d\n",i);
        threadpool_add(thp, process, (void*)&num[i]);     /* (把客户端作为生产者的内容加到任务队列里,而任务队列对应线程池中的每一个线程)向线程池中添加任务 */
    }
    sleep(10);                                          /* 等子线程完成任务,销毁他 */
    threadpool_destroy(thp);

    return 0;
}

#endif

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值