当前位置: 首页 > news >正文

手撕线程池详解(C语言源码+解析)

池化技术是实际开发中经常使用的一种编程技巧,它的核心思想是:提前申请出适量的计算机资源(如内存、线程、进程、数据库连接等),以便程序在运行过程中重复地使用它们,而不是每次需要时都临时申请。

举个简单的例子,C 语言支持用 malloc() 函数申请指定大小的内存空间(堆内存),当程序中需要频繁使用堆内存时,有以下两种实现方案:

  • 通常的做法:每次需要使用堆内存的时候,都用 malloc() 申请,用完后再及时用 free() 函数释放;
  • 池化技术:提前调用 malloc() 函数申请足量的堆内存,程序中可以重复使用它,最终用 free() 函数释放掉。 

两种方法最大的区别在于,在程序的整个执行过程中,前者会多次申请和释放内存资源,而后者只需要申请和释放一次。

要知道,申请和释放计算机资源的过程,通常是很耗时的。池化技术通过重复使用提前申请好的计算机资源,提高资源利用率的同时,大大减少了申请和释放资源的次数,程序的整体性能会更佳。

程序里能够使用的计算机资源有很多,比如线程、进程、内存、数据库连接等。相对应地,常见的池化技术有线程池、进程池、内存池、数据库连接池等。

由此,线程池就很容易理解了,它是常见的一种池化技术,核心就是提前创建好适量的线程,然后重复利用它们去完成多个任务,而不是为每个任务单独创建一个线程。

例如,以多线程的方式执行 1000 个任务,对比以下两种实现方案:

  • 普通方案:创建 1000 个线程,每个线程负责执行 1 个任务;
  • 线程池实现:只创建少量的线程(比如 5 个、10 个),每个线程执行完一个任务后继续执行下一个,通过重复使用这些线程,最终完成 1000 个任务。

频繁创建和销毁线程的过程,是非常耗费时间和系统资源的。相比之下,线程池通过重复利用少量的线程,大大减少了创建和销毁线程的次数,既省时又节约资源,程序的性能更佳。

在多线程编程中,可以优先考虑使用线程池,尤其是在需要处理大量任务的场景中。

线程池的基本构成

对于初次接触线程池的读者,通常会认为线程池很复杂,其实不然,下图展示了线程池的基本构成:

图 1 线程池的基本构成

线程池由两部分构成,分别是:

  • 任务队列:当外界传递的任务数量较多时,线程池需要暂时将所有任务存储起来,后续分发给各个线程;
  • 多个线程:线程池需要管理多个线程,包括它们的创建、销毁和同步等。

也就是说,管理着多个线程,重复利用它们去执行外界传递进来的诸多任务,这就是一个完整的线程池。

当然,线程池的构成不是一成不变的,可以根据实际场景的需要进行合理的扩展。例如,可以在线程池中增添一个管理者线程,如下图所示:

图 2 带管理者线程的线程池

这里将线程池中的多个线程称为工作线程,以便和管理者线程区分开。管理者线程负责实时调整工作线程的数量:

  • 在某一时刻,如果绝大部分的工作线程都处于忙碌状态,且任务队列中还有大量未处理的任务,管理者线程会额外创建一些新的工作线程;
  • 在某一时刻,如果绝大部分的工作线程都处于空闲状态,管理者线程会销毁一部分工作线程。

接下来就带领大家实现图 2 所示的线程池。

线程池的具体实现(下载源码包)

考虑到一些读者的编程基础薄弱,接下来从创建源文件开始,详细讲解线程池的实现过程(直接获取源码:pan.quark.cn/s/95578b27171d)。

1、新建 3 个源文件

新建 3 个源文件(有些 IDE 可能要先创建项目),分别命名为 threadpool.h、threadpool.c 和 main.c:

  • threadpool.h:存放一些函数的定义,方便在 main.c 文件中调用实现的线程池;
  • threadpool.c:存放线程池的实现代码;
  • main.c:编写 main() 主函数,调用写好的线程池。
2、自定义结构体表示线程池

结合图 2,线程池中至少包含以下成员:

  1. 任务队列:对于外界传递进来的任务,可以存储到数组里,也可以存储到链表里;
  2. 多个线程:工作线程可以存储到数组中;
  3. 管理者线程:单独定义一个 pthread_t 类型的变量表示管理者线程;
  4. 实现线程同步的锁:工作线程之间很可能出现“竞争资源”的情况,本节采用互斥锁和条件变量实现线程同步。

其中,任务队列里存放的任务需要单独用一个结构体表示。所谓任务,其实就是让工作线程执行的函数,因此表示任务的结构体为:

/**
*  系统学习C语言 https://xiecoding.cn/c/
**/
struct Task
{void (*function)(void* arg);  // 指向一个函数void* arg;                    // 记录函数的参数struct Task* next;            // 指向下一个任务结点
};

任务队列可以用数组或者链表实现,这里我们选择链表,因此结构体中包含了指向下一个结点的 next 指针。此外,读者可以自行设计 function 所指函数的返回值类型、参数类型以及参数的个数。

下面是表示线程池的结构体:

/**
*  系统学习C语言 https://xiecoding.cn/c/
**/
struct ThreadPool
{Task* taskQ;                // 任务队列,用链表实现Task* queueFront;           // 队头 -> 取数据Task* queueRear;            // 队尾 -> 放数据int queueCapacity;          // 线程池容纳任务的最大容量int queueSize;              // 当前任务的数量pthread_t managerID;        // 管理者线程IDpthread_t* threadIDs;       // 存放工作线程的数组int busyNum;                // 忙碌的线程个数int liveNum;                // 存活的线程个数int exitNum;                // 要销毁的线程个数pthread_mutex_t mutexPool;  // 线程池互斥锁pthread_mutex_t mutexBusy;  // 专门为busyNum设置的互斥锁pthread_cond_t noFull;      // 任务队列满了pthread_cond_t noEmpty;     // 任务队列空了int shutdown;               // 是否要销毁线程池, 销毁为 1, 不销毁为 0
};

1) 链表 taskQ 表示任务队列,秉着“先传递进线程池的任务,优先交给工作线程执行”的原则,定义了两个辅助变量 queueFront 和 queueRear:

  • queueFront:记录最后进入的任务在 taskQ 里的位置;
  • queueRear:记录最先进入的任务在 taskQ 里的位置。

对于传递进线程池的任务,根据 queueFront 记录的位置将它们存储到 taskQ 里;所有的工作线程,全部根据 queueRear 记录的位置从 taskQ 里获取任务。由此,就能保证优先处理最早传递进线程池的任务。

2) managerID 代表管理者线程。数组 threadIDs 用来存储工作线程。考虑到管理者线程要实时调整工作线程的数量,特意添加了 busyNum、liveNum 和 exitNum 这 3 个辅助变量:

  • liveNum:记录当前线程池中一共有多少个工作线程;
  • busyNum:记录当前有多少个正在执行任务的工作线程;
  • exitNum:当 exitNum 为非 0 值时,表示管理者线程要销毁指定数量的工作线程。

管理者线程增加或者销毁工作线程,主要取决于 liveNum 和 busyNum 的值,具体的实现过程先跳过,后续会给大家讲解。

3) 为了防止工作线程出现“竞争资源”的情况,我们为线程池添加了 mutexPool 互斥锁,考虑到 busyNum 变量的值会频繁地变化,还专门为变量 busyNum 添加了 mutexBusy 互斥锁。

4) 当任务队列空了的时候,需要阻塞工作线程;当有新的任务进入任务队列时,需要唤醒工作线程;当任务队列满了的时候,需要阻塞外界添加任务的线程。因此,线程池中添加了 noFull 和 noEmpty 这两个条件变量。

5) shutdown 用来记录线程池是否存在,它的存在也是很有必要的。

结构体 Task 和 ThreadPool 的实现代码可以编写在 threadpool.c 文件中,也可以写在 threadpool.h 文件中。 

3、编写threadpool.h文件

要想在 main.c 文件里使用线程池,threadpool.h 文件里需要提供一下可供调用的函数(函数的声明),包括:

  • threadPoolCreate():创建一个线程池;
  • threadPoolDestory():销毁一个线程池;
  • threadPoolAdd():往线程池中添加任务。

下面是 threadpool.h 文件中的代码:

/**
*  系统学习C语言 https://xiecoding.cn/c/
**/
#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include <pthread.h>typedef struct Task Task;
typedef struct ThreadPool ThreadPool;// 创建线程池
ThreadPool* threadPoolCreate(int threadNUM);
// 销毁线程池
int threadPoolDestroy(ThreadPool* pool);
// 向线程池中添加任务
void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg);#endif

由于将 Task 和 ThreadPool 结构体的定义写到了 threadpool.c 文件中,所以 threadpool.h 文件中需要额外声明一下。

这就意味着,只要我们在 threadpool.c 文件中实现了这 3 个函数,线程池就编写完成了。

4、实现threadPoolCreate() 函数

所谓创建线程池,其实就是初始化一个 ThreadPool 结构体类型的变量,下面先给出 threadPoolCreate() 函数的实现代码(位于 threadpool.c 文件里):

/**
*  系统学习C语言 https://xiecoding.cn/c/
**/
// 创建线程池,threadNUM 为要创建的线程数量
ThreadPool* threadPoolCreate(int threadNUM)
{ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool));do{if (pool == NULL){printf("threadpool malloc fail...\n");break;}// 创建存储线程的数组,方便管理线程pool->threadIDs = (pthread_t*)malloc(sizeof(pthread_t) * MAX_NUM);if (pool->threadIDs == NULL){printf("threadIDs malloc fail...\n");break;}// 全部初始化 -1,表示当前位置没有存放线程memset(pool->threadIDs, -1, sizeof(pthread_t) * MAX_NUM);pool->busyNum = 0;pool->liveNum = threadNUM;    // 和最小个数相等pool->exitNum = 0;// 初始化互斥锁和条件变量if (pthread_mutex_init(&pool->mutexPool, NULL) != 0 ||pthread_mutex_init(&pool->mutexBusy, NULL) != 0 ||pthread_cond_init(&pool->noEmpty, NULL) != 0 ||pthread_cond_init(&pool->noFull, NULL) != 0){printf("mutex or condition init fail...\n");break;}// 创建任务链表pool->taskQ = (Task*)malloc(sizeof(Task));pool->taskQ->next = NULL;pool->queueFront = pool->queueRear = pool->taskQ;pool->queueCapacity = QUEUE_CAPACITY;pool->queueSize = 0;// shutdown 为 0,表示线程池存在pool->shutdown = 0;// 创建管理者线程pthread_create(&pool->managerID, NULL, manager, pool);// 创建工作线程for (int i = 0; i < threadNUM; ++i){pthread_create(&pool->threadIDs[i], NULL, worker, pool);}return pool;} while (0);// 释放资源if (pool && pool->threadIDs) {free(pool->threadIDs);}if (pool && pool->taskQ) {free(pool->taskQ);}if (pool) {free(pool);}return NULL;
}

从一开始,我们创建一个 ThreadPool 类型的指针 pool,并为它申请了堆内存。后续的所有操作,都是在初始化 pool 的结构体成员,代码比较容易理解,也附带了详尽的注释,这里不带过多赘述。

有两点需要特别说明,分别是:

  • 函数最终会把创建好的 pool 作为返回值,因此我们必须为它申请堆内存,这样才能保证函数执行结束后 pool 不会被销毁;
  • 程序中巧妙地使用了 do while(0) 结构,循环条件是 0,所以循环体只执行 1 次。这样做的好处是,一旦有结构体成员初始化失败,则直接跳出循环(break),对于先前申请过的内存资源,它们的释放工作都放在 do while(0) 的外部,代码的逻辑看上去非常清晰、有条理。

也可以不用 do while(0) 结构,大家可以自行尝试修改,然后对比两种写法,自然能体会到 do while(0) 的好处。

在 threadPoolCreate() 函数里,指定管理者线程 managerID 执行 manager() 函数,同时所有的工作线程执行 worker() 函数,这两个函数也需要我们自己编写实现。

5、实现worker()函数

worker() 是所有工作线程执行的函数,梳理一下它的实现思路:

  1. 判断当前是否有可执行的任务,如果没有则等待,直至外界传递进来新的任务;反之,则开始执行任务;
  2. 从任务链表中取下一个任务;
  3. 执行第 2 步取下的任务;
  4. 任务执行完成后,重新回到第 1 步。

实现 worker() 函数的难点在于,所有的工作线程一起操作 pool 里的任务链表和其它结构体成员,因此需要用到互斥锁和条件变量,防止发生“资源竞争”。

下面是 worker() 函数的实现代码:

/**
*  系统学习C语言 https://xiecoding.cn/c/
**/
// 线程工作函数
void* worker(void* arg)
{ThreadPool* pool = (ThreadPool*)arg;while (1){pthread_mutex_lock(&pool->mutexPool);// 判断当前是否有可执行的任务while (pool->queueSize == 0 && !pool->shutdown){// 阻塞工作线程pthread_cond_wait(&pool->noEmpty, &pool->mutexPool);// 管理者要销毁线程时,会将 exitNUM 的值设置为 2if (pool->exitNum > 0){pool->exitNum--;if (pool->liveNum > MIN_NUM){pool->liveNum--;pthread_mutex_unlock(&pool->mutexPool);threadExit(pool); // 销毁当前线程}}}// 如果线程池不存在,直接销毁当前线程if (pool->shutdown){pthread_mutex_unlock(&pool->mutexPool);threadExit(pool);}// 从任务队列中取出一个任务Task* t = pool->queueFront->next;pool->queueFront->next = t->next;if (pool->queueFront->next == NULL) {pool->queueRear = pool->queueFront;}pool->queueSize--;// 解锁pthread_mutex_unlock(&pool->mutexPool);pthread_cond_signal(&pool->noFull);printf("线程 %ld 开始执行任务...\n", pthread_self());// 调整忙碌线程的数量pthread_mutex_lock(&pool->mutexBusy);pool->busyNum++;pthread_mutex_unlock(&pool->mutexBusy);t->function(t->arg);free(t);t = NULL;printf("线程 %ld 执行任务结束...\n", pthread_self());// 调整忙碌线程的数量pthread_mutex_lock(&pool->mutexBusy);pool->busyNum--;pthread_mutex_unlock(&pool->mutexBusy);}return NULL;
}

说明以下几点:
1) 判断当前是否有可执行的任务时,之所以用 while() 循环而不是 if 判断语句,是因为当条件变量被触发时,pthread_cond_wait() 函数通常会唤醒一个线程,但不排除会有多个线程被唤醒。使用 while 循环的好处是,可以确保每个线程在继续执行前重新检查条件是否真正满足。

在 while 循环中,还添加了销毁线程的 if 语句,暂时不理解也没关系,可以先跳过,等实现 manager() 函数时再回过头来理解。

2) 从任务链表中摘取任务之前,各个工作线程都要确保线程池是存在的,如果线程池被销毁,则当前线程直接终止即可。

3) 从任务链表上成功摘下任务后,要及时触发 noFull 条件变量,以便外界继续往任务链表上添加新的任务。

4) 当工作线程执行任务时,它属于忙碌的线程,要及时更新 pool->busyNum 的值,以便管理者线程调整工作线程的数量。

6、实现manager()函数

只要线程池不被销毁,管理者线程就会根据 pool->liveNum 和 pool->busyNum 的值实时调整工作线程的数量。

下面是 manager() 函数的实现代码:

/**
*  系统学习C语言 https://xiecoding.cn/c/
**/
// 管理者线程要执行的函数
void* manager(void* arg)
{ThreadPool* pool = (ThreadPool*)arg;// 只要线程池不销毁,管理者线程就一直执行while (!pool->shutdown){// 每隔2秒检测一次sleep(2);// 如果线程池销毁,立即停止运行if (pool->shutdown == 1) {break;}// 打印当前的线程数量,以及忙碌线程的数量printf("liveNum:%d,busyNum:%d\n", pool->liveNum, pool->busyNum);// 获取线程池中任务的数量和线程的总数量pthread_mutex_lock(&pool->mutexPool);int queueSize = pool->queueSize;int liveNum = pool->liveNum;pthread_mutex_unlock(&pool->mutexPool);// 获取忙碌线程的数量pthread_mutex_lock(&pool->mutexBusy);int busyNum = pool->busyNum;pthread_mutex_unlock(&pool->mutexBusy);// 比较任务数量和线程数量,决定是否创建新的线程if (liveNum * 2 < queueSize && liveNum < MAX_NUM){pthread_mutex_lock(&pool->mutexPool);int counter = 0;for (int i = 0; i < MAX_NUM && counter < NUMBER&& pool->liveNum < MAX_NUM; ++i){if (pool->threadIDs[i] == -1){pthread_create(&pool->threadIDs[i], NULL, worker, pool);printf("管理者线程添加了 1 个新线程 \n");counter++;pool->liveNum++;}}pthread_mutex_unlock(&pool->mutexPool);}// 如果大多数线程都是空闲的线程,则销毁 NUMBER 个线程if (busyNum * 2 < liveNum && liveNum > MIN_NUM){pthread_mutex_lock(&pool->mutexPool);pool->exitNum = NUMBER;pthread_mutex_unlock(&pool->mutexPool);// 让工作的线程自杀for (int i = 0; i < NUMBER; ++i){pthread_cond_signal(&pool->noEmpty);}}}return NULL;
}

程序中通过在 while 循环中添加 sleep(2),使得管理者线程每隔 2 秒执行一次。 

需要注意的是,当条件busyNum * 2 < liveNum && liveNum > MIN_NUM成立时,管理者线程会销毁两个存活的线程,实现方案是让两个存活的线程自我销毁。管理者线程会触发两次 noEmpty 条件变量,从而唤醒两个阻塞在 worker() 函数中的线程,此时 pool->exitNum 的值为 2,两个线程就会进入第 15 行的 if 语句,最终执行 threadExit() 函数结束。

ThreadExit() 也是一个自定义的函数,在线程执行结束之前,还需要将其占用的 threadIDs 数组空间重置为 -1,以便新的工作线程使用。该函数的具体实现,后续会做详细地解释。

7、实现threadPoolDestroy()函数

threadPoolDestroy() 函数的功能是销毁线程池,恰好和 threadPoolCreate() 函数相反。

销毁线程池,要完成以下几步:

  • 手动释放掉在 threadPoolCreate() 函数里申请的堆内存;
  • 等待管理者线程和所有的工作线程执行结束;
  • 销毁互斥锁和条件变量。

下面是 threadPoolDestroy() 函数的实现代码:

/**
*  系统学习C语言 https://xiecoding.cn/c/
**/
// 销毁线程池
int threadPoolDestroy(ThreadPool* pool)
{if (pool == NULL){return -1;}// 提前复制一份 pool->threadID,方便后续调用 pthread_join() 函数等待各个线程执行结束。pthread_mutex_lock(&pool->mutexPool);pthread_t* threadIDs = (pthread_t*)malloc(sizeof(pthread_t) * MAX_NUM);memcpy(threadIDs, pool->threadIDs, sizeof(pthread_t) * MAX_NUM);pthread_mutex_unlock(&pool->mutexPool);// 关闭线程池pool->shutdown = 1;// 阻塞回收管理者线程pthread_join(pool->managerID, NULL);// 唤醒阻塞的工作线程for (int i = 0; i < pool->liveNum; i++){pthread_cond_signal(&pool->noEmpty);}// 回收所有工作的线程for (int i = 0; i < MAX_NUM; i++) {if (threadIDs[i] != -1) {pthread_join(threadIDs[i], NULL);}}// 释放任务链表while (pool->taskQ && pool->taskQ->next) {Task* t = pool->taskQ->next;pool->taskQ->next = t->next;free(t);}if (pool->taskQ){free(pool->taskQ);}// 释放pool->threadIDs占用的堆内存if (pool->threadIDs){free(pool->threadIDs);}// 释放 threadIDs 数组占用的堆内存if (threadIDs) {free(threadIDs);}// 销毁互斥锁和条件变量pthread_mutex_destroy(&pool->mutexPool);pthread_mutex_destroy(&pool->mutexBusy);pthread_cond_destroy(&pool->noEmpty);pthread_cond_destroy(&pool->noFull);// 销毁 pool 线程池free(pool);pool = NULL;return 0;
}

注意,当唤醒所有的工作线程以后,它们都会去执行 threadExit() 函数,此函数内部会修改 pool->threadIDs 数组的数据。如果我们想调用 pthread_join() 函数等待所有工作线程执行结束,需要 pool->threadIDs 数组记录的数据作为参数,所以程序中事先把 pool->threadIDs 数组拷贝了一份。

8、实现threadPoolAdd()函数

threadPoolAdd() 函数要实现的功能是向线程池中添加一个新任务,所以它的函数原型是:

void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg)

pool 是创建好的线程池,func 是添加的新任务(本质就是一个函数),arg 是传递给函数的参数。

向线程池中添加一个新任务,其实就是将新任务作为一个结点,把它添加到 pool 的任务链表上。下面是 threadPoolAdd() 函数的具体实现:

/**
*  系统学习C语言 https://xiecoding.cn/c/
**/
// 向线程池中添加任务
void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg)
{pthread_mutex_lock(&pool->mutexPool);// 如果线程池被销毁,直接返回if (pool->shutdown){pthread_mutex_unlock(&pool->mutexPool);return;}// 如果线程池已满,堵塞当前线程,直至线程池中有空闲空间。while (pool->queueSize == pool->queueCapacity && !pool->shutdown){// 阻塞线程添加任务pthread_cond_wait(&pool->noFull, &pool->mutexPool);}// 添加任务,往任务链表中添加新结点Task* t = (Task*)malloc(sizeof(Task));t->function = func;t->arg = arg;t->next = NULL;pool->queueRear->next = t;pool->queueRear = t;pool->queueSize++;pthread_mutex_unlock(&pool->mutexPool);pthread_cond_signal(&pool->noEmpty);
}

threadPoolAdd() 函数的实现过程比较简单,这里不再赘述。

9、实现threadExit()函数

threadExit() 函数的功能是让指定的工作线程终止执行。

正常情况下,终止线程执行直接调用 pthread_exit() 函数即可,当它并不能满足我们的需要。

为了方便管理所有的工作线程,我们将它们全部存储到 pool->threadIDs 数组中。初始状态下,pool->threadIDs 数组里存放的都是 -1,表示所有的空间都是空闲的,都能用来存储线程。当某个工作线程想要终止执行,我们需要手动将它占用的数组空间重置为 -1,这样 pool->threadIDs 数组的空间才能重复使用,为后续存放新的线程做准备。

下面是 threadExit() 函数的实现代码:

/**
*  系统学习C语言 https://xiecoding.cn/c/
**/// 销毁线程的函数
void threadExit(ThreadPool* pool)
{pthread_mutex_lock(&pool->mutexPool);pthread_t tid = pthread_self();for (int i = 0; i < MAX_NUM; ++i){if (pool->threadIDs[i] == tid){// 将 threadIDs 对应位置重置为 1,以便此空间能重复利用。pool->threadIDs[i] = -1;printf("%ld 线程被销毁\n", tid);break;}}pthread_mutex_unlock(&pool->mutexPool);pthread_exit(NULL);
}
10、threadpool.c中的宏

线程池中的很多限定条件,比如工作线程的最小数量和最大数量、任务链表的最大长度、管理者线程每次调整的线程数量等,可以用宏来表示。

下面是 threadpool.c 中的宏:

#define NUMBER 2 // 管理者线程每次调整的线程数量
#define MIN_NUM 1 // 线程最小数量
#define MAX_NUM 20 // 线程最大数量
#define QUEUE_CAPACITY 100 // 任务队列的最大长度
11、编写main.c文件

完全以上 10 步操作,就已经成功地实现了线程池。大家可以在 main.c 文件编写测试程序,验证你实现的线程池是否能够正常工作。

下面是 main.c 文件中的代码:

/**
*  系统学习C语言 https://xiecoding.cn/c/
**/
#include <stdio.h>
#include "threadpool.h"
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>// 线程要执行的函数
void taskFunc(void* arg) {printf("线程 %ld 正在工作,number =%d\n", pthread_self(), *(int*)arg);sleep(2);
}int main()
{// 创建线程池,线程数量位于 [1,20] 之间ThreadPool* pool = threadPoolCreate(10);// 添加任务for (int i = 0; i < 100; i++) {int* num = (int*)malloc(sizeof(int));*num = i;threadPoolAdd(pool, taskFunc, (void*)num);}// 等待所有线程执行结束sleep(30);// 销毁线程池threadPoolDestroy(pool);return 0;
}

注意,为了保证所有任务都被线程池执行完,main() 函数中添加了 sleep() 函数,最后再调用 threadPoolDestory() 函数销毁线程池。

当线程初始数量为 1,任务数量为 10 个时,程序的执行过程是:

线程 139643818604288 开始执行任务...
线程 139643818604288 正在工作,number =0
线程 139643818604288 执行任务结束...
线程 139643818604288 开始执行任务...
线程 139643818604288 正在工作,number =1
liveNum:1,busyNum:1
管理者线程添加了 1 个新线程
管理者线程添加了 1 个新线程
线程 139643801818880 开始执行任务...
线程 139643801818880 正在工作,number =2
线程 139643810211584 开始执行任务...
线程 139643810211584 正在工作,number =3
线程 139643818604288 执行任务结束...
线程 139643818604288 开始执行任务...
线程 139643818604288 正在工作,number =4
线程 139643810211584 执行任务结束...
线程 139643810211584 开始执行任务...
线程 139643810211584 正在工作,number =5
liveNum:3,busyNum:3
线程 139643801818880 执行任务结束...
线程 139643801818880 开始执行任务...
线程 139643801818880 正在工作,number =6
liveNum:3,busyNum:3
线程 139643818604288 执行任务结束...
线程 139643818604288 开始执行任务...
线程 139643818604288 正在工作,number =7
线程 139643810211584 执行任务结束...
线程 139643810211584 开始执行任务...
线程 139643810211584 正在工作,number =8
线程 139643801818880 执行任务结束...
线程 139643801818880 开始执行任务...
线程 139643801818880 正在工作,number =9
线程 139643818604288 执行任务结束...
线程 139643810211584 执行任务结束...
liveNum:3,busyNum:1
139643810211584 线程被销毁
139643818604288 线程被销毁
线程 139643801818880 执行任务结束...
liveNum:1,busyNum:0
......
liveNum:1,busyNum:0
139643801818880 线程被销毁

总结

线程池是常见的一种池化技术,它的核心思想是:提前创建好适量的线程,然后重复利用它们去完成多个任务,而不是为每个任务单独创建一个线程。

线程池的优势在于,它能够重复利用少数的线程去执行大量的任务,提高线程利用率的同时,大大减少了创建和销毁线程的系统开销,从而提供程序的性能。

在多线程编程中,可以优先考虑使用线程池,尤其是在需要处理大量任务的场景中。


文章转载自:
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://.
http://www.dtcms.com/a/281615.html

相关文章:

  • 35.KMP 算法
  • 分发糖果-leetcode
  • Kafka亿级消息资源组流量掉零故障排查
  • 【LLIE专题】通过通道选择归一化提升模型光照泛化能力
  • MySQL 8.0 OCP 1Z0-908 题目解析(25)
  • 【QT】实现应用程序启动画面
  • 笔试——Day9
  • linux kernel的错误编码指针详细介绍
  • 【深度学习新浪潮】什么是任意倍率超分?
  • 知识库信息切片,AI降本增效的利刃
  • Unity灯光面板环境设置
  • [Python] -实用技巧6-Python中with语句和上下文管理器解析
  • 身份核验自动化-姓名身份证号二要素核验接口-API实名验证
  • 计算机系统方向可发会议/期刊参考时间
  • CF1916D Mathematical Problem 题解
  • 数据结构——顺序表的相关操作
  • TypeScript之旅
  • 读取ubuntu的磁盘分区表与超级块
  • 万字长文深度解析:AI搜索范式背后的四大核心模块
  • Typescript 泛型
  • 智慧跳绳全方案:三模无线+姿态感知,低功耗高精度芯片选型指南OM6626/SI24R2E
  • 【flex布局】
  • Python实现按数字命名合并文本文件
  • 推客小程序开发全景指南:从商业模式到用户体验的完整方法论
  • 前端开发数据缓存方案详解
  • Spring Boot全局异常处理:打造坚如磐石的应用防线
  • C++ - 仿 RabbitMQ 实现消息队列--muduo快速上手
  • 【每日刷题】螺旋矩阵
  • 【Python】定时器快速实现
  • 并发编程-volatile