一个简单线程池的实现

来源:互联网 发布:360wifi网络不稳定 编辑:程序博客网 时间:2024/05/16 14:01

什么是线程池???

1,包含若干个线程,是线程的集合

  线程池当中的线程个数不是越多越少,太多了会增加系统的开销。太少了又会降低并发量。

  所以线程池当中的线程的个数有严格的要求

  若干个线程通常都小于并发的任务量

2,线程池中的线程用于执行大量的相对短暂的任务

  当某一个时刻来了大量的并发任务时,我们要用若干个相对较少的线程来调度这些大量的任务。。。由于线程的  个数小于任务的数量,因而任务需要进入到队列当中进行等待。但是,我们总能够在相对短暂的时间之内来得到

  一个线程对任务进行处理。。。

3,线程池执行的是相对短暂的任务。如果一个任务要执行的时间太长,那么不适合放在线程池当中进行处理。

  如:假设一个任务的执行时间是跟进程的生命周期是一致的,那么这个时候,这个任务的处理,就没有必要放在

    线程池当中的线程来进行调度。用一个普通的线程即可

4,那么线程池当中的线程多大才是合适的呢???

  实际上:这跟任务的类型有关系。

  1,计算密集型任务,是占用cpu的,很少被外界的事件打断。那么这个时候,线程池当中的线程的个数等于

           cpu个数是最佳的。那么即使比cpu的个数多,那也不能多太多(为什么呢???)

           原因在于:如果线程的个数超过了cpu个数。那么这个时候由于cpu的个数是一定的

           ,这意味着能够并发的数目是一定的。那么这个时候就会用到相对少量的cpu个数来调度

           多个线程,那么这一定会引起线程与线程之间的切换问题,产生切换开销(上下文的切换

           ,需要有一定的开销,降低效率)

           对于计算密集型任务来说:很少阻塞

  2,I/O密集型任务,当正在执行任务的时候,可能会被I/O中断,也就是说:这个线程可能会被挂起。

           对于这种情况来说:线程的个数要大于cpu的个数(这才是合理的)。

           我们来看一个具体的分析:假设cpu的个数为2,分配的线程个数也等于2.

           那么这个时候,两个线程都可以执行I/O任务,但是呢,很可能出现这样一种情况,

                                       这两个线程都阻塞在了I/O(发生了I/O),那么这个时候呢???

           该线程就让出了cpu的控制权,如果这个时候,新来了多个任务,但是由于线程的个数

           只有两个,并且让出了cpu的控制权,也就是说:系统当中,有两个cpu可以用,但是

           由于线程的个数只有两个,即使新来了任务也没用办法得到执行。因而对于i/o密集型任

           务而言,线程的个数必须大于cpu个数。

5,当任务增加的时候,能够动态的增加线程池中线程的数量直到达到一个阈值(也就是说:线程池当中的线程

   有一个总量)

6,当任务执行完毕的时候,我们也能动态的销毁线程池当中的线程

7,线程池的实现,本质上也是生产者,消费者模型的一种应用。

   任务的增加,我们可以看做生产者线程向任务队列中添加任务,那么线程池一旦检测到有任务的到来,这个时

   候就会唤醒线程池当中等待的线程来执行任务(就相当于消费者来进行消费这些任务)。当然,如果这个时候

   没有任何一个线程处于等待的状态,并且线程数没有达到阈值,线程池就会创建一个新的线程来执行任务。。

   那么这个新的线程也是消费者线程。。。


完整版程序:

condition.c(一个新封装的条件变量)

#include "condition.h"int condition_init(condition_t *cond){int status;if((status = pthread_mutex_init(&cond->pmutex, NULL)))return status;if((status = pthread_cond_init(&cond->pcond, NULL)))return status;return 0;}int condition_lock(condition_t *cond){return pthread_mutex_lock(&cond->pmutex);}int condition_unlock(condition_t *cond){return pthread_mutex_unlock(&cond->pmutex);}int condition_wait(condition_t *cond){return pthread_cond_wait(&cond->pcond, &cond->pmutex);}int condition_timedwait(condition_t *cond, const struct timespec *abstime){return pthread_cond_timedwait(&cond->pcond, &cond->pmutex, abstime);}int condition_signal(condition_t *cond){return pthread_cond_signal(&cond->pcond);}int condition_broadcast(condition_t *cond){return pthread_cond_broadcast(&cond->pcond);}int condition_destroy(condition_t *cond){int status;if((status = pthread_mutex_destroy(&cond->pmutex)))return status;if(status = pthread_cond_destroy(&cond->pcond))return status;return 0;}

condition.h

#ifndef _CONDITION_H_#define _CONDITION_H_#include <pthread.h>typedef struct condition{pthread_mutex_t pmutex;pthread_cond_t pcond;}condition_t;//条件变量总是跟一个互斥锁进行使用int condition_init(condition_t *cond);//初始化一个条件变量(包括锁和变量)int condition_lock(condition_t *cond);int condition_unlock(condition_t *cond);//加锁和解锁都是对于内部的互斥锁而言的int condition_wait(condition_t *cond);//在条件变量之上等待条件int condition_timedwait(condition_t *cond,const struct timespec *abstime);//具有超时功能的一个等待函数int condition_signal(condition_t *cond);//向等待线程发起一个通知int condition_broadcast(condition_t *cond);//广播int condition_destroy(condition_t *cond);//销毁条件变量#endif  

pthreadpool.c
#include "threadpool.h"#include <stdlib.h>#include <stdio.h>#include <string.h>#include <errno.h>#include <time.h>void *thread_routine(void *arg){struct timespec abstime;int timeout;printf("thread 0x%x is on starting\n", (int)pthread_self());//就是说:这个线程是一个消费者线程,会去等待任务threadpool_t *pool = (threadpool_t *)arg;while(1){timeout = 1;condition_lock(&pool->ready);pool->idle++;   //空闲的线程会去加1//处于等待的状态,等待队列当中,有任务的到来//同时等待线程池销毁的通知//当如上两个条件不满足的时候,就进行等待while(pool->first == NULL && !pool->quit){printf("thread 0x%x is waiting\n", (int)pthread_self());//condition_wait(&pool->ready);//对于线程池当中的线程而言,如果任务执行完成,那么就应该自动的销毁//获取当前的时间clock_gettime(CLOCK_REALTIME, &abstime);//设置超时为2秒abstime.tv_sec += 2;int status = condition_timedwait(&pool->ready, &abstime);   //超时等待if(status == ETIMEDOUT){printf("thread 0x%x is wait timed out\n", (int)pthread_self());timeout = 1;//超时标志为1break;}}//等待到条件,处于工作状态pool->idle --;//等待到了任务if(pool->first != NULL){//从对头取出任务task_t *t = pool->first;pool->first = t->next;//如果这时候我们去执行任务,还是处于锁的状态//执行任务需要一定的时间,此刻还是一个锁的状态//而且生产者也没用办法往队列中添加任务了//因而,我们这边需要解锁,以便其它消费者线程能够进来等待任务condition_unlock(&pool->ready);t->run(t->arg);//当线程执行完,重新加锁//因为执行任务需要一定的时间free(t);condition_lock(&pool->ready); }//如果等待到了线程池销毁通知,且任务都执行完毕了if(pool->quit && pool->first == NULL){pool->counter--;if(pool->counter == 0){condition_signal(&pool->ready);}condition_unlock(&pool->ready);//跳出循环一定要解锁break;}//超时并且没有了任务if(timeout && pool->first == NULL){pool->counter--;condition_unlock(&pool->ready);break;}condition_unlock(&pool->ready);}printf("thread 0x%x is exting\n", (int)pthread_self());return NULL;}//初始化线程池void threadpool_init(threadpool_t *pool, int threads){//对线程池中的各个字段进行初始化condition_init(&pool->ready);pool->first = NULL;pool->last = NULL;pool->counter = 0;pool->idle = 0;pool->max_threads = threads;pool->quit = 0;}//往线程池里面添加任务void threadpool_add_task(threadpool_t *pool, void *(*run)(void *arg), void *arg){//往线程池当中添加任务,我们首先要生成一个任务task_t *newtask = (task_t *)malloc(sizeof(task_t));newtask->run = run;newtask->arg = arg;newtask->next = NULL;//进行互斥,因为生产者和消费者都可以访问他condition_lock(&pool->ready);//将任务添加到队列当中(队尾)if(pool->first == NULL)  //第一次添加pool->first = newtask;elsepool->last->next = newtask;pool->last = newtask;        //接下来,判断一下有没有等待线程,如果有,唤醒这些线程        //来消费这些任务if(pool->idle > 0)//有线程处于等待状态,则唤醒其中一个,让他来执行任务{condition_signal(&pool->ready);}else if(pool->counter < pool->max_threads){//没有等待的线程,并且当前线程数不超过最大线程数,就创建一个新的线程pthread_t tid;pthread_create(&tid, NULL, thread_routine, pool);//一旦我们创建这样一个线程,那么线程数就要加1pool->counter ++;}condition_unlock(&pool->ready);}void threadpool_destroy(threadpool_t *pool){if(pool->quit){return ;}condition_lock(&pool->ready);pool->quit = 1;//线程池当中的线程的个数if(pool->counter > 0){//线程池当中有线程//处于等待状态的线程,处于运行态的线程//如果是等待状态的线程,我们可发送一个广播//运行态的线程不会接收到广播if(pool->idle > 0){//等待状态的线程condition_broadcast(&pool->ready);}//执行态的线程不会收到广播,//我们的线程池需要等待执行态中的线程全部退出while(pool->counter > 0){condition_wait(&pool->ready);}}condition_unlock(&pool->ready);condition_destroy(&pool->ready);}

threadpool.h

#ifndef _THREAD_POOL_H_#define _THREAD_POOL_H_#include "condition.h"//任务结构体,将任务放入队列由线程池中的线程来执行typedef struct task{        void *(*run)(void *arg);   //任务回调函数        void *arg;                 //回调函数参数        struct task *next;}task_t;typedef struct threadpool{        condition_t ready;   //任务准备就绪或者线程池销毁通知        //condition 是一个已经封装过的条件变量        task_t *first;//任务队列头指针        task_t *last;        //任务队列尾指针        int counter;         //线程池中当前的线程数        int idle;            //线程池中当前正在等待任务的线程数        int max_threads;     //线程池中最大允许的线程数        int quit;            //销毁线程池的时候置为1}threadpool_t;//初始化线程池void threadpool_init(threadpool_t *pool, int threads);//往线程池中添加任务void threadpool_add_task(threadpool_t *pool, void *(*run)(void *arg), void *arg);//销毁线程池void threadpool_destroy(threadpool_t *pool);#endif   

main.c

#include "threadpool.h"#include <unistd.h>#include <stdio.h>#include <stdlib.h>void *mytask(void *arg){//添加任务后执行任务printf("thread 0x%x is working on task %d\n", (int)pthread_self(), *(int*)arg);//任务执行需要时间sleep(1);        free(arg);return NULL;}int main(void){threadpool_t pool;   //定义一个线程池对象threadpool_init(&pool, 3);//线程池的初始化,开始为3个线程        //接下来,我们可以把主线程看作是生产者线程,要往线程池当中添加任务int i;for(i = 0; i< 10; i++){int *arg = (int *)malloc(sizeof(int));*arg = i;//分配地址的目的是防止出错threadpool_add_task(&pool, mytask, arg);}//往线程池当中添加10个任务//sleep(15);threadpool_destroy(&pool);return 0;}

Makefile:

.PHONY:cleanCC=gccCFLAGS=-Wall -gALL=mainall:$(ALL)OBJS=threadpool.o main.o condition.o.c.o:$(CC) $(CFLAGS) -c $<main:$(OBJS)$(CC) $(CFLAGS) $^ -o $@ -lpthread -lrt -gclean:rm -f $(ALL) *.O






0 0