转自https://blog.csdn.net/hubi0952/article/details/8045094

线程池的基本原理

在传统的服务器结构中,常用一个总的线程监听有没有新的客户端连接服务器。每当有一个新的客户端连接就开启一个新的线程处理这个客户端的信息,这个线程只服务于这个用户,当客户端和服务器关闭连接后服务器端就销毁这个线程。

当服务器频繁的有客户端连接的时候就要频繁的开辟与销毁线程极大的占用了系统的资源。在大量用户的情况下开辟和销毁线程将浪费大量的时间和资源。线程池提供了一个解决外部大量用户与服务器有限资源的矛盾。线程池和传统的一个用户对应一个线程的处理方法不同,它的基本思想就是在程序开始时就在内存中开辟一些线程,线程的数目是固定的,它们独自形成一个类,屏蔽了对外的操作,而服务器只需要将数据包交给线程池就可以了。当有新的客户请求到达时,不是新创建一个线程为其服务,而是从线程池中选择一个空闲的线程为新的客户请求服务,服务完毕后线程进入空闲线程池中。如果没有线程空闲你的话就将数据报暂时积累,等待线程池内有线程空闲以后再进行处理。

通过对多个任务重用已经存在的线程对象,降低了对线程对象的创建和销毁的开销。当客户请求时,由于线程对象已经存在,可以提高请求时间。

线程池的组成部分:

1、线程管理器:用于创建并管理线程池

2、工作线程:线程池中实际执行任务的线程。在初始化线程时会预先创建好固定数目的线程在线程池中,这些初始化的线程一般处于空闲状态(阻塞(睡眠)),不占用CPU,占用较小的内存空间。

3、每个任务必须实现的接口,当线程池中的任务队列(任务链表)中有可执行任务时,被空闲的工作线程调用执行(线程的闲与忙是通过互斥量实现的)。把任务抽象出来形成接口,可做到线程距具体的任务无关。

4、任务队列:用来存放没有处理的任务,提供一种缓冲机制,实现这种结构有好几种方法,常用的有队列,利用队列的先进先出原理。

什么时候需要线程池?

如果一个应用需要频繁的创建和销毁线程,而任务执行的时间又非常短,这样线程创建和销毁带来的开销就不容忽视。这时候就需要线程池了。如果线程创建的和销毁的时间相比任务执行时间可以忽略不计,则没有必要使用线程池。

在Linux系统下用C语言创建一个线程池。线程池会维护一个任务链表(每个CThread_worker结构就是一个任务)。

任务队列的结构和线程池的结构

/*
*线程池里所有运行和等待的队列都是一个CThread_worker结构
*由于所有的CThread_worker结构都在队列中,所以是队列中的一个节点
*/
typedef struct worker
{/*回调函数,当任务运行时会调用此函数,也可以声明为其他形式*/void *(*process)(void *arg);/*回调函数的参数*/void *arg;              struct worker *next;
}CThread_worker;/*线程池的结构*/
typedef struct
{pthread_mutex_t queue_lock;pthread_cond_t queue_ready;/*指向任务等待队列的队头*/CThread_worker *queue_head;/*是否销毁线程池*/int shutdown;/*线程ID,使用堆空间来分配内存*/pthread_t *threadid;/*线程池中线程的数目*/int max_thread_num;/*当前等待队列的任务数目*/int cur_queue_size;
}CThread_pool;

pool_init()(线程池初始化函数预先创建好max_thread_num个线程),每个线程执行thread_routine()函数。

pool_init()实现

void pool_init(int max_thread_num)
{pool = (CThread_pool *)malloc(sizeof(CThread_pool));if(pool == NULL){printf("pool_init1\n");    exit(-1);}/*初始化线程互斥锁和条件变量*/pthread_mutex_init(&(pool->queue_lock), NULL);pthread_cond_init(&(pool->queue_ready), NULL);pool->queue_head = NULL;    pool->shutdow = 0;pool->max_thread_num = max_thread_num;pool->threadid = (pthread *)malloc(sizeof(pthread) * max_thread_num);if(pool->threadid == NULL){printf("pool_init2\n");    exit(-1);}/*初始化任务队列为0*/        pool->cur_queue_size = 0;int i = 0;/*创建max_thread_num数目的线程*/for(i-0; i<max_thread_num; i++){pthread_create(&pool->threadid[i], NULL, thread_routine, NULL)}
}

thread_routine的实现(每个线程都执行的函数)

void *thread_routine(void *arg)
{printf("starting thread 0x%x\n", pthread_self());while(1){/*因为线程中访问到临界资源(任意时刻只允许一个线程访问的资源),所以要上锁*/pthread_mutex_lock(&pool->queue_lock);/*如果任务等待队列为空则线程阻塞,使用了条件变量*/while(pool->cur_queue_size == 0 && !pool->shutdown){/*pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/printf("thread 0x%x is waiting\n", pthread_self());pthread_cond_wait(&(pool->queue_ready), &(pool->queue_lock));}if(shutdown){/*遇到break,continue,return等跳转语句,千万不要忘记先解锁*/pthread_mutex_unlock(&(pool->queue_lock));printf("thread x%x will exit\n", phread_self());pthread_exit();}printf("thread 0x%x is starting to work\n", pthread_self());/*从任务队列中取出任务*/CThread_worker *worker = pool->queue_head;pool->queue_head = worker->next;    pool->cur_queue_size--;/*访问临界资源结束,要解锁,以便其他线程访问*/pthread_mutex_unlock(&(pool->queue_lock));/*执行任务等待队列中的任务*/worker->process(worker->arg);free(worker);worker = NULL;}pthread_exit(NULL);
}

pool_add_worker()函数向线程池的任务队列中加入一个任务,加入后通过调用pthread_cond_signal (&(pool->queue_ready))唤醒一个处于阻塞状态的线程(如果有的话)

void pool_add_worker(void *(*process)(void *arg), void *arg)
{//为新的任务分配内存,然后添加到任务队列中CThread_woker *newwork = (CThread_worker *)malloc(sizeof(CThread_worker));if(newwork == NULL){printf("pool_add_worker\n");exit(-1);}newwork->process = process;newwork->arg = arg;newwork->next = NULL;    /*访问到临界资源要上锁*/pthread_mutex_lock(&(pool->queue_lock));/*将新任务插入到队尾*/CThread_worker *temp = pool->queue_head;if(temp == NULL){pool->queue_head = newwork;}else{while(temp->next != NULL){temp = temp->next;}temp->next = newwork;}/*任务等待队列的任务数加1*/pool->cur_queue_size++;/*解锁*/pthread_mutex_unlock(&(pool->queue_lock));/*发信号唤醒任意一个空闲的线程去处理新加入的任务*/pthread_cond_signal(&(pool->queue_ready));}

pool_destroy ()函数用于销毁线程池,线程池任务链表中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出。

void pool_destory()
{if(pool->shutdown){return -1; /*防止两次调用*/}pool->shutdown = 1;/*唤醒所有等待的线程*/pthread_cond_broadcast(&(pool->queue_ready));/*阻塞等待线程退出,否则子线程先退出,主线程没有回收就变成僵尸线程了*/int i;for(i=0; i<pool->max_thread_num; i++){pthread_join(pool->threadid[i], NULL);}free(pool->threadid);pool->threadid = NULL;/*销毁等待队列*/CThread_worker *temp;while(pool->queue_head != NULL){temp = pool->queue_head;pool->queue_head = temp->next;free(temp);}/*销毁条件互斥锁和条件变量*/pthread_mutex_destory(&(pool->queue_lock));pthread_cond_destory(&(pool->queue_ready));free(pool);/*销毁后指针置空*/pool = NULL;return 0;
}

测试函数

void *myprocess (void *arg)
{printf ("threadid is 0x%x, working on task %d\n", pthread_self (),*(int *) arg);sleep (1);/*休息一秒,延长任务的执行时间*/return NULL;
}int
main (int argc, char **argv)
{pool_init (5);/*线程池中最多三个活动线程*//*连续向池中投入10个任务*/int *workingnum = (int *) malloc (sizeof (int) * 10);int i;for (i = 0; i < 10; i++){workingnum[i] = i;pool_add_worker (myprocess, &workingnum[i]);}/*等待所有任务完成*/sleep (5);/*销毁线程池*/pool_destroy ();free (workingnum);return 0;
}

/*----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/

线程池的目的是为了减少线程创建、销毁所带来的代价,当有非常多的任务需要独立的线程去做的时候,可以使用线程池,从线程池中唤醒一个空闲(睡眠状态)线程来处理一个个的任务。

线程池中的每个子线程都是等价的。用线程信号量来控制子线程和任务的分配问题。设置一个信号量来表示任务队列中的任务资源,每个子线程都会处于死循环中,每轮循环首先等待一个任务资源信号量,当等到之后,互斥的从任务队列中摘取一个任务节点。任务节点中记录着该任务所要执行的函数指针及其参数。之后子线程开始执行该任务。执行完后释放一个信号量并进去下一轮循环。当信号量小于1(没有信号量)时,子线程将会阻塞。

因此一个任务由哪一个线程执行,要看那个线程能够获取到对应的信号量资源。

具体实现:

任务队列由双向链表构造,每个节点包含一个任务的函数指针和参数指针。

一般一个简单的线程池有下列组件:

1、线程池管理器(用于创建并管理线程池)

2、工作线程(线程池中的线程)

3、任务接口(task,每个任务必须实现的接口,以供工作线程调度任务的执行)

4、任务队列(用于存放没有处理的任务。提供一种缓冲机制)

线程池工作的基本逻辑:

1.首先 线程池初始化时 会创建出很多条线程,但他们没任务可执行时,就会调用条件变量cond,让自己沉睡。因此线程池一被创建,就躺着很多条沉睡的线程。

2.线程的执行函数中,有个while(1)循环,让线程有任务时执行任务,没任务时就调用pthread_cond_wait()来沉睡。

3.当有任务加入线程池的任务列表时,会通过调用pthread_cond_signal()来唤醒一条线程(add_task()函数),然后线程执行完就继续执行下一条任务或沉睡。

4.当线程池中的 shundown变量变成true时,便会调用pthread_cond_broadcase()唤醒所有沉睡的线程,使线程们自己退出

C语言实现简单的线程池【转】相关推荐

  1. C语言实现的简单的线程池

    http://www.linuxidc.com/Linux/2013-01/77619.htm 有时我们会需要大量线程来处理一些相互独立的任务,为了避免频繁的申请释放线程所带来的开销,我们可以使用线程 ...

  2. 【C/C++开发】C++实现简单的线程池

    C++实现简单的线程池 线程池编程简介: 在我们的服务端的程序中运用了大量关于池的概念,线程池.连接池.内存池.对象池等等.使用池的概念后可以高效利用服务器端的资源,比如没有大量的线程在系统中进行上下 ...

  3. 分享:一个简单的线程池的实现

    一个简单的线程池的实现 http://my.oschina.net/hejiula/blog/110519

  4. 手写一个简单的线程池MyThreadPool

    说明 手写的一个简单的线程池,旨在帮助了解线程池的工作原理. 核心内容 核心工作线程 任务阻塞队列 定义一个内部类去实现核心工作线程 /*** 内部类:工作的核心线程*/private final c ...

  5. linux下c语言线程传参数,【linux】C语言多线程中运行线程池,在线程池中运行线程池,,传递的结构体参数值为空/NULL/0...

    C语言多线程中运行线程池,在线程池中运行线程池,,传递的结构体参数值为空/NULL/0 本贴问题,之前已经提问过一次,当时已经解决了,原贴在这里https://segmentfault.com/q/1 ...

  6. 一个简单的线程池设计方案

    一个简单的线程池本质上是生产者-消费者模型,一般是线程池负责消费任务,任务分配线程负责生产任务,任务可以由队列.链表或全局变量等数据结构承担.如果生产和消费速度差不多,可以采用环形队列结构:如果任务有 ...

  7. linux下C语言简单实现线程池

    0 前言 网上关于线程池的例子还是不少,简单明了的倒是比较少,看了网上的资料,打算借鉴网上的一些例子,自己实现以下. 线程的概念就不多说,首先说一下多线程的好处:多线程技术主要解决处理器单元内多个线程 ...

  8. 简单C++线程池包装类源码示例

    这里给出一个简单的C++线程池包装类,该类具有的特点是: 1.线程池大小是固定的, 一创建后,就不具有伸缩特性. 一般建议是 CPU核心数的2倍或1倍. 2.简单但是很可靠. 3.资源占用极低. 在开 ...

  9. java基础:简单实现线程池

    先上原理图:为了更好的在手机上显示,我重新把图画了一遍 上代码之前,要先补充一下线程池构造的核心几个点 线程池里的核心线程数与最大线程数 线程池里真正工作的线程worker 线程池里用来存取任务的队列 ...

最新文章

  1. 张涵20160401作业
  2. c++入门代码_C/C++编程笔记:C语言入门题之正倒金字塔,正反三角形代码详解
  3. 服务器操作系统用什么好,服务器操作系统一般用什么
  4. SpringCloud 入门教程(六): 用声明式REST客户端Feign调用远端HTTP服务
  5. mysql面向对象例子_PHP 面向对象实例:获取数据库用户数据
  6. 通达信公式转python为什么很难_转行数据分析为什么这么难?
  7. Spring-core-SpringFactoriesLoader类
  8. ubuntu之解决安装python3.6.4后出现error while loading shared libraries: libpython3.6m.so.1.0的问题
  9. 述职答辩提问环节一般可以问些什么_陕西省高级职称评审,90%的人都“死”在答辩上?...
  10. jxl freemark
  11. java分页的方法_java实现的分页方法(上一页下一页)
  12. texstudio语法检查
  13. Linux 30岁,这些年经历了什么?
  14. timeout参数使用,转贴
  15. Python3基础--18--数据库编程(上)
  16. 清华物理系与计算机系哪个好,兰州大学现在还有物理系吗?
  17. oracle修改数据文件
  18. 数据结构:“大根堆、小根堆”的向上调整算法和向下调整算法
  19. DNS 劫持和DNS 污染
  20. Android studio 多渠道(多环境)打包grade配置详解

热门文章

  1. 小程序连接优声云打印机流程
  2. SpringBoot整合Mybatis-Plus
  3. 动态设置html字号,html动态字体
  4. 基于麻雀算法的无人机航迹规划 - 附代码
  5. 快速渡河(贪心算法)
  6. 动态可视化十大排序算法之冒泡排序
  7. 伦敦 quant_伦敦统一用户组(LUUG)见面v2.0
  8. delphi删除文本内容_文本编辑器EmEditor快捷键指令
  9. 仿鱼爪新媒交易账号过户转让平台源码担保第三方账号交易系统公众号服务号抖音快手小红书
  10. 快牙网传——轻松传文件