1. 总结理解
线程池的是什么?
线程池就好比水龙头的水流入一个较大的水池,当需要水的时候,5个人挑着扁担,每个人同时可以去打10桶水(各自打各自的2通水,互不影响),然后挑着就走。如果没有水池,那么每个人都需要在从水龙头上接自己的2桶水,谁先到水就先打水,这样打水的效率极低,5个人都需要排队打10桶水。
所以:线程池,顾名思义就是由多个线程组成的“水池”,当有任务需要执行时,由空闲线程进行处理,若没有任务,则线程处于休眠等待的状态。
什么时候需要创建线程池呢?(为什么要使用线程池?)
如果一个应用程序需要频繁创建、销毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了;
如果线程创建和销毁时间相比任务执行时间可以忽略不计,则没有必要使用线程池了。
比如线程池有3个线程,那调用线程池的程序中,至少会有几个线程呢?(如下实例)
至少会有4个线程,因为主进程也是一个线程。
2. 参考blog:
线程池基本原理:
在传统服务器结构中,常是有一个总的 监听线程监听有没有新的用户连接服务器, 每当有一个新用户进入,服务器就开启一个新的线程用户处理这 个用户的数据包。这个线程只服务于这个用户,当 用户与服务器端关闭连接以后,服务器端销毁这个线程。然而频繁地开辟与销毁线程极大地占用了系统的资源。而且在大量用户的情况下,系统为了开辟和销毁线程将浪费大量的时间和资源。
线程池提供了一个解决外部大量用户与服务器有限资源的矛盾,线程池和传统的一个用户对应一 个线程的处理方法不同,它的基本思想就是在程序开始时就在内存中开辟一些线程,线程的数目是固定的,他们独自形成一个类,屏蔽了对外的操作,而服务器只需要将数据包交给线程池就可以了。当有新的客户请求到达时,不是新创建一个线程为其服务,而是从“池子”中选择一个空闲的线程为新的客户请求服务,服务完毕后,线程进入空闲线程池中。如果没有线程空闲 的 话, 就将数据包 暂时积累 , 等待线程池内有线 程空闲以后再进行处理。通过对多个任务重用已经存在的线程对象 , 降低了对线程对象创建和销毁的开销。当客户请求时 , 线程对象已经存在 , 可以提高请求的响应时间 , 从而整体地提高了系统服务的表现。
另一种线程池描述
大多数的网络服务器,包括Web服务器都具有一个特点,就是单位时间内必须处理数目巨大的连接请求,但是处理时间却是比较短的。在传统的多线程服务器模型中是这样实现的:一旦有个请求到达,就创建一个新的线程,由该线程执行任务,任务执行完毕之后,线程就退出。这就是"即时创建,即时销毁"的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数非常频繁,那么服务器就将处于一个不停的创建线程和销毁线程的状态。这笔开销是不可忽略的,尤其是线程执行的时间非常非常短的情况。
线程池就是为了解决上述问题的,它的实现原理是这样的:在应用程序启动之后,就马上创建一定数量的线程,放入空闲的队列中。这些线程都是处于阻塞状态,这些线程只占一点内存,不占用CPU。当任务到来后,线程池将选择一个空闲的线程,将任务传入此线程中运行。当所有的线程都处在处理任务的时候,线程池将自动创建一定的数量的新线程,用于处理更多的任务。执行任务完成之后线程并不退出,而是继续在线程池中等待下一次任务。当大部分线程处于阻塞状态时,线程池将自动销毁一部分的线程,回收系统资源。
linux下C 线程池的原理讲解和代码实现(能自行伸缩扩展线程数) https://blog.51cto.com/lonelyc/1315434
一般来说实现一个线程池主要包括以下几个组成部分:
1)线程管理器:用于创建并管理线程池。
2)工作线程:线程池中实际执行任务的线程。在初始化线程时会预先创建好固定数目的线程在池中,这些初始化的线程一般处于空闲状态,一般不占用CPU,占用较小的内存空间。
3)任务接口:每个任务必须实现的接口,当线程池的任务队列中有可执行任务时,被空闲的工作线程调去执行(线程的闲与忙是通过互斥量实现的,跟前面文章中的设置标志位差不多),把任务抽象出来形成接口,可以做到线程池与具体的任务无关。
4)任务队列:用来存放没有处理的任务,提供一种缓冲机制,实现这种结构有好几种方法,常用的是队列,主要运用先进先出原理,另外一种是链表之类的数据结构,可以动态的为它分配内存空间,应用中比较灵活,下文中就是用到的链表。
在Linux系统下用C语言创建的一个线程池的步骤:
1) 线程池会维护一个任务链表(每个CThread_worker结构就是一个任务)。
2) pool_init()函数预先创建好max_thread_num个线程,每个线程执thread_routine ()函数。该函数中
while (pool->cur_queue_size == 0) {
pthread_cond_wait (&(pool->queue_ready),&(pool->queue_lock));
}
表示如果任务链表中没有任务,则该线程出于阻塞等待状态。否则从队列中取出任务并执行。
3) pool_add_worker()函数向线程池的任务链表中加入一个任务,加入后通过调用pthread_cond_signal (&(pool->queue_ready))唤醒一个出于阻塞状态的线程(如果有的话)。
4) pool_destroy ()函数用于销毁线程池,线程池任务链表中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出。
————————————————————————————————————————————————————————
https://blog.csdn.net/ygl840455828ygl/article/details/52351003
线程池的创建和使用(总结1) ???
https://blog.csdn.net/li646495946/article/details/106904631
3. 应用实例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <assert.h>
/*
* 线程池里所有运行和等待的任务都是一个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; /*是否销毁线程池*/
pthread_t *threadid;
int max_thread_num; /*线程池中允许的活动线程数目*/
int cur_queue_size; /*当前等待队列的任务数目*/
} CThread_pool;
int pool_add_worker(void *(*process) (void *arg), void *arg);
void *thread_routine(void *arg);
static CThread_pool *g_pool = NULL;
void pool_init(int max_thread_num)
{
g_pool = (CThread_pool *)malloc(sizeof (CThread_pool));
pthread_mutex_init (&(g_pool->queue_lock), NULL);
pthread_cond_init (&(g_pool->queue_ready), NULL);
g_pool->queue_head = NULL;
g_pool->max_thread_num = max_thread_num;
g_pool->cur_queue_size = 0;
g_pool->shutdown = 0;
g_pool->threadid = (pthread_t *) malloc (max_thread_num * sizeof (pthread_t));
int i = 0;
for (i = 0; i < max_thread_num; i++) {
pthread_create(&(g_pool->threadid[i]), NULL, thread_routine, NULL);
}
}
/* 向线程池中加入任务 */
int pool_add_worker(void *(*process)(void *arg), void *arg)
{
/* 构造一个新任务, 并初始化 */
CThread_worker *newworker = (CThread_worker *) malloc (sizeof (CThread_worker));
newworker->process = process;
newworker->arg = arg;
newworker->next = NULL;
pthread_mutex_lock (&(g_pool->queue_lock));
/* 将任务加入到等待队列中 */
CThread_worker *tmpHead = g_pool->queue_head;
if (tmpHead != NULL) {
while (tmpHead->next != NULL) {
tmpHead = tmpHead->next;
}
tmpHead->next = newworker;
} else {
g_pool->queue_head = newworker;
}
assert (g_pool->queue_head != NULL);
g_pool->cur_queue_size++;
pthread_mutex_unlock (&(g_pool->queue_lock));
printf("*** pthread 0x%x signal, arg: (%d) addr %p ***\n", pthread_self(), *(int *)arg, (int *)arg);
/* 等待队列中有任务了,唤醒一个等待线程;
注意如果所有线程都在忙碌,这句没有任何作用 */
pthread_cond_signal (&(g_pool->queue_ready));
return 0;
}
/* 销毁线程池,等待队列中的任务不会再被执行,
但是正在运行的线程会一直把任务运行完后再退出 */
int pool_destroy(void)
{
if (g_pool->shutdown) {
return -1;/*防止两次调用*/
}
g_pool->shutdown = 1;
/*唤醒所有等待线程,线程池要销毁了*/
pthread_cond_broadcast (&(g_pool->queue_ready));
/*阻塞等待线程退出,否则就成僵尸了*/
int i;
for (i = 0; i < g_pool->max_thread_num; i++) {
pthread_join (g_pool->threadid[i], NULL);
}
free (g_pool->threadid);
/*销毁等待队列*/
CThread_worker *head = NULL;
while (g_pool->queue_head != NULL) {
head = g_pool->queue_head;
g_pool->queue_head = g_pool->queue_head->next;
free (head);
}
/*条件变量和互斥量也别忘了销毁*/
pthread_mutex_destroy(&(g_pool->queue_lock));
pthread_cond_destroy(&(g_pool->queue_ready));
free (g_pool);
/*销毁后指针置空是个好习惯*/
g_pool=NULL;
return 0;
}
void *thread_routine(void *arg)
{
printf ("[starting thread] 0x%x\n", pthread_self());
while (1) {
pthread_mutex_lock (&(g_pool->queue_lock));
/*如果等待队列为0并且不销毁线程池,则处于阻塞状态;
* 注意: pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/
while (g_pool->cur_queue_size == 0 && !g_pool->shutdown) {
printf ("thread 0x%x is waiting\n", pthread_self());
pthread_cond_wait(&(g_pool->queue_ready), &(g_pool->queue_lock));
}
if (g_pool->shutdown) {
pthread_mutex_unlock (&(g_pool->queue_lock));
printf ("thread 0x%x will exit\n", pthread_self());
pthread_exit (NULL);
}
printf ("thread 0x%x is starting to work\n", pthread_self());
/*assert是调试的好帮手*/
assert (g_pool->cur_queue_size != 0);
assert (g_pool->queue_head != NULL);
g_pool->cur_queue_size--;
CThread_worker *worker = g_pool->queue_head;
g_pool->queue_head = worker->next;
pthread_mutex_unlock (&(g_pool->queue_lock));
/* 调用回调函数,执行任务 */
(*(worker->process)) (worker->arg);
free (worker);
worker = NULL;
}
printf("pthread_exit (NULL)?????\n");
/*这一句应该是不可达的*/
pthread_exit (NULL);
}
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(3);/*线程池中最多三个活动线程*/
sleep(1);
/*连续向池中投入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;
}
含有线程的应用程序编译时,需要加 -pthread,表示引用gcc编译使用了POSIX thread的库函数
(pthread是动态库,需要用-lpthread,所有的动态库都需要用-lxxx来引用gcc编译使用了POSIX thread的程序时通常需要加额外的选项)
程序执行结果如下:
[zll@zll linux_dataStruct]$ gcc 01_pool.c -o 01_pool -pthread
[zll@zll linux_dataStruct]$
[zll@zll linux_dataStruct]$
[zll@zll linux_dataStruct]$
[zll@zll linux_dataStruct]$ ./01_pool
[starting thread] 0xb637fb70
thread 0xb637fb70 is waiting
[starting thread] 0xb6d80b70
thread 0xb6d80b70 is waiting
[starting thread] 0xb7781b70
thread 0xb7781b70 is waiting
*** pthread 0xb77826c0 signal, arg: (0) addr 0x8481228 ***
*** pthread 0xb77826c0 signal, arg: (1) addr 0x848122c ***
*** pthread 0xb77826c0 signal, arg: (2) addr 0x8481230 ***
*** pthread 0xb77826c0 signal, arg: (3) addr 0x8481234 ***
*** pthread 0xb77826c0 signal, arg: (4) addr 0x8481238 ***
*** pthread 0xb77826c0 signal, arg: (5) addr 0x848123c ***
*** pthread 0xb77826c0 signal, arg: (6) addr 0x8481240 ***
*** pthread 0xb77826c0 signal, arg: (7) addr 0x8481244 ***
*** pthread 0xb77826c0 signal, arg: (8) addr 0x8481248 ***
*** pthread 0xb77826c0 signal, arg: (9) addr 0x848124c ***
thread 0xb637fb70 is starting to work
threadid is 0xb637fb70, working on task 0
thread 0xb6d80b70 is starting to work
threadid is 0xb6d80b70, working on task 1
thread 0xb7781b70 is starting to work
threadid is 0xb7781b70, working on task 2
thread 0xb637fb70 is starting to work
threadid is 0xb637fb70, working on task 3
thread 0xb6d80b70 is starting to work
threadid is 0xb6d80b70, working on task 4
thread 0xb7781b70 is starting to work
threadid is 0xb7781b70, working on task 5
thread 0xb637fb70 is starting to work
threadid is 0xb637fb70, working on task 6
thread 0xb6d80b70 is starting to work
threadid is 0xb6d80b70, working on task 7
thread 0xb7781b70 is starting to work
threadid is 0xb7781b70, working on task 8
thread 0xb637fb70 is starting to work
threadid is 0xb637fb70, working on task 9
thread 0xb6d80b70 is waiting
thread 0xb7781b70 is waiting
thread 0xb637fb70 is waiting
thread 0xb6d80b70 will exit
thread 0xb7781b70 will exit
thread 0xb637fb70 will exit
[zll@zll linux_dataStruct]$