基础知识传送门:


小抄一波概念:

啥是线程池?

线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

C语言多线程编程(四)——线程池 - 知乎 - 图1

来欣赏张图[截取网上]


再看下线程池的常规结构,也成劝退体。很多人一看这个东西头就大,内容太多,直接不看了。下面来挑战下吧。

  1. typedef struct threadpool_t {
  2. pthread_mutex_t lock; /* 用于锁住本结构体 ,和条件变量一起使用 */
  3. pthread_mutex_t thread_counter; /* 记录忙状态线程个数的锁 -- busy_thr_num */
  4. pthread_cond_t queue_not_full; /* 当任务队列满时,添加任务的线程阻塞,等待此条件变量 */
  5. pthread_cond_t queue_not_empty; /* 任务队列里不为空时,通知线程池中等待任务的线程 */
  6. pthread_t *threads; /* 存放线程池中每个线程的tid。数组 */
  7. pthread_t adjust_tid; /* 存管理线程tid */
  8. threadpool_task_t *task_queue; /* 任务队列 */
  9. int min_thr_num; /* 线程池最小线程数 */
  10. int max_thr_num; /* 线程池最大线程数 */
  11. int live_thr_num; /* 当前存活线程个数 */
  12. int busy_thr_num; /* 忙状态线程个数 */
  13. int wait_exit_thr_num; /* 要销毁的线程个数 */
  14. int queue_front; /* task_queue队头下标 */
  15. int queue_rear; /* task_queue队尾下标 */
  16. int queue_size; /* task_queue队中实际任务数 */
  17. int queue_max_size; /* task_queue队列可容纳任务数上限 */
  18. int shutdown; /* 标志位,线程池使用状态,true或false */
  19. } threadpool_t;

对常规代码我进行了一些小优化修改,不过还有点问题待解决。

已修改:

删除 pthread_cond_broadcast 冗余广播

  1. /*通知所有的空闲线程, 不需要循环广播*
  2. /pthread_cond_broadcast(&(pool->queue_not_empty));

修改线程池的释放的逻辑,无需死等或者任务未完成却 exit 掉

  1. /* 释放线程池 */
  2. if (busyNum == 0 && taskNum == 0)
  3. {
  4. threadpool_destroy(thp);
  5. return 0;
  6. }

待解决:

threadpool_add, 在堵塞阶段且都为耗时任务时, 会极低概率的情况出现 arg 还在使用却 free 掉了的安全问题(这一块的逻辑不太好改,暂时没更好的思路)

  1. /* 清空 工作线程 调用的回调函数 的参数arg */
  2. if (pool->task_queue[pool->queue_rear].arg != NULL)
  3. {
  4. free(pool->task_queue[pool->queue_rear].arg);//这里调用free不安全,因为有可能线程执行函数中, 忙符合时低概率
  5. pool->task_queue[pool->queue_rear].arg = NULL;
  6. }

完整的功能实现总共快 400 行了,就不贴在这里了,给你们一个 gayhub 连接,也看的舒服。
https://zhuanlan.zhihu.com/p/141615042