参考:linux C 线程池
链接:https://blog.csdn.net/llzhang_fly/article/details/116140236
线程池就好比水龙头的水流入一个较大的水池,当需要水的时候,5个人挑着扁担,每个人同时可以去打10桶水(各自打各自的2通水,互不影响),然后挑着就走。如果没有水池,那么每个人都需要在从水龙头上接自己的2桶水,谁先到水就先打水,这样打水的效率极低,5个人都需要排队打10桶水。
所以:线程池,顾名思义就是由多个线程组成的“水池”,当有任务需要执行时,由空闲线程进行处理,若没有任务,则线程处于休眠等待的状态。
如果一个应用程序需要频繁创建、销毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了;
如果线程创建和销毁时间相比任务执行时间可以忽略不计,则没有必要使用线程池。
比如线程池有3个线程,那调用线程池的程序中,至少会有几个线程呢?
至少会有4个线程,因为主进程也是一个线程。
在传统服务器结构中,常是有一个总的监听线程(listen函数)监听有没有新的用户连接服务器, 每当有一个新用户进入,服务器就开启一个新的线程用户处理这个用户的数据包。这个线程只服务于这个用户,当用户与服务器端关闭连接以后,服务器端销毁这个线程。然而频繁地开辟与销毁线程极大地占用了系统的资源。而且在大量用户的情况下,系统为了开辟和销毁线程将浪费大量的时间和资源。
线程池提供了一个解决外部大量用户与服务器有限资源的矛盾,线程池和传统的一个用户对应一 个线程的处理方法不同,它的基本思想就是在程序开始时就提前在内存中开辟一些线程,线程的数目是固定的,他们独自形成一个类,屏蔽了对外的操作,而服务器只需要将数据包交给线程池就可以了。当有新的客户请求到达时,不是新创建一个线程为其服务,而是从“池子”中选择一个空闲的线程为新的客户请求服务,服务完毕后,线程进入空闲线程池中。如果没有线程空闲的话, 就将数据包暂时积累 , 等待线程池内有线程空闲以后再进行处理。通过对多个任务重用已经存在的线程对象 , 降低了对线程对象创建和销毁的开销。当客户请求时 , 线程对象已经存在 , 可以提高请求的响应时间 , 从而整体地提高了系统服务的表现。
另一种线程池描述
大多数的网络服务器,包括Web服务器都具有一个特点,就是单位时间内必须处理数目巨大的连接请求,但是处理时间却是比较短的。在传统的多线程服务器模型中是这样实现的:一旦有个请求到达,就创建一个新的线程,由该线程执行任务,任务执行完毕之后,线程就退出。这就是"即时创建,即时销毁"的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数非常频繁,那么服务器就将处于一个不停的创建线程和销毁线程的状态。这笔开销是不可忽略的,尤其是线程执行的时间非常非常短的情况。
线程池就是为了解决上述问题的,它的实现原理是这样的:在应用程序启动之后,就马上创建一定数量的线程,放入空闲的队列中。这些线程都是处于阻塞状态,这些线程只占一点内存,不占用CPU。当任务到来后,线程池将选择一个空闲的线程,将任务传入此线程中运行。当所有的线程都处在处理任务的时候,线程池将自动创建一定的数量的新线程,用于处理更多的任务。执行任务完成之后线程并不退出,而是继续在线程池中等待下一次任务。当大部分线程处于阻塞状态时,线程池将自动销毁一部分的线程,回收系统资源。
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 ()函数用于销毁线程池,线程池任务链表中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出。
#include
#include
#include
#include
#include
#include
/*
* 线程池里所有运行和等待的任务都是一个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); /*线程池中最多3个活动线程*/
// 3个人去做10件事情
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的程序时通常需要加额外的选项)
root@cloudserver:~/robot/code/dock_ble_ota/M146470-zgltest# ./a.out
[starting thread] 0x4a204700
thread 0x4a204700 is waiting
[starting thread] 0x4aa05700 // 分配这3个人去做10件事
thread 0x4aa05700 is waiting
[starting thread] 0x4b206700
thread 0x4b206700 is waiting
*** pthread 0x4ba2f740 signal, arg: (0) addr 0x55d0fd556660 ***
*** pthread 0x4ba2f740 signal, arg: (1) addr 0x55d0fd556664 ***
thread 0x4a204700 is starting to work [我要开始干活了]
threadid is 0x4a204700, working on task {0} [一秒钟活干完了]
*** pthread 0x4ba2f740 signal, arg: (2) addr 0x55d0fd556668 ***
thread 0x4aa05700 is starting to work [我要开始干活了]
threadid is 0x4aa05700, working on task {1} [一秒钟活干完了]
*** pthread 0x4ba2f740 signal, arg: (3) addr 0x55d0fd55666c ***
thread 0x4b206700 is starting to work [我要开始干活了]
threadid is 0x4b206700, working on task {2} [一秒钟活干完了]
*** pthread 0x4ba2f740 signal, arg: (4) addr 0x55d0fd556670 ***
*** pthread 0x4ba2f740 signal, arg: (5) addr 0x55d0fd556674 ***
*** pthread 0x4ba2f740 signal, arg: (6) addr 0x55d0fd556678 ***
*** pthread 0x4ba2f740 signal, arg: (7) addr 0x55d0fd55667c ***
*** pthread 0x4ba2f740 signal, arg: (8) addr 0x55d0fd556680 ***
*** pthread 0x4ba2f740 signal, arg: (9) addr 0x55d0fd556684 ***
thread 0x4a204700 is starting to work [我要开始干活了]
threadid is 0x4a204700, working on task {3} [一秒钟活干完了]
thread 0x4aa05700 is starting to work [我要开始干活了]
threadid is 0x4aa05700, working on task {4} [一秒钟活干完了]
thread 0x4b206700 is starting to work [我要开始干活了]
threadid is 0x4b206700, working on task {5} [一秒钟活干完了]
thread 0x4a204700 is starting to work [我要开始干活了]
threadid is 0x4a204700, working on task {6} [一秒钟活干完了]
thread 0x4aa05700 is starting to work [我要开始干活了]
threadid is 0x4aa05700, working on task {7} [一秒钟活干完了]
thread 0x4b206700 is starting to work [我要开始干活了]
threadid is 0x4b206700, working on task {8} [一秒钟活干完了]
thread 0x4a204700 is starting to work [我要开始干活了]
threadid is 0x4a204700, working on task {9} [一秒钟活干完了]
thread 0x4aa05700 is waiting
thread 0x4b206700 is waiting
thread 0x4a204700 is waiting
thread 0x4aa05700 will exit
thread 0x4b206700 will exit
thread 0x4a204700 will exit
https://subingwen.cn/linux/threadpool/