Linux系统编程系列之线程池

 Linux系统编程系列(16篇管饱,吃货都投降了!)

        1、Linux系统编程系列之进程基础

        2、Linux系统编程系列之进程间通信(IPC)-信号

        3、Linux系统编程系列之进程间通信(IPC)-管道

        4、Linux系统编程系列之进程间通信-IPC对象

        5、Linux系统编程系列之进程间通信-消息队列

        6、Linux系统编程系列之进程间通信-共享内存

        7、Linux系统编程系列之进程间通信-信号量组

        8、Linux系统编程系列之守护进程

        9、Linux系统编程系列之线程

        10、Linux系统编程系列之线程属性 

        11、Linux系统编程系列之互斥锁和读写锁

        12、Linux系统编程系列之线程的信号处理

        13、Linux系统编程系列之POSIX信号量

        14、Linux系统编程系列之条件变量

        15、Linux系统编程系列之死锁

        16、 Linux系统编程系列之线程池

一、什么是线程池

        线程池就是将许多线程,放置在一个池子中(实际就是一个结构体)只要有任务,就将任务投入池中,这些线程们通过某些机制,及时处理这些任务,一旦处理完后又重新回到池子中重新接收新任务。为了便于管理,线程池还应当提供诸如初始化线程池,增删线程数量,检测未完成任务的数据,检测正在执行任务的线程的数量、销毁线程池等等基本操作。

二、特性

        1、更好的满足并发性需求

        2、更加节省系统资源,降低负载

        3、避免每次需要执行任务时都创建新的线程

        4、可以限制并发线程数量,帮助控制应用程序的性能和稳定性

        核心思想:通过精巧的设计使得池子中的线程数量可以动态地发生变化,让线程既可以应对并发性需求,又不会浪费系统资源

三、使用场景

        用来应对某种场景,要在程序中创建大量线程,并且这些线程的数量和生命周期均不确定,可能方生方死,也可能常驻内存,请看下面举例。

        1、处理大量密集型任务

        线程池可以处理多个任务,从而减少了创建和销毁线程的开销,提高了系统性能

        2、服务端应用程序

        线程池可以帮助服务端应用程序同时处理多个客户端请求

        3、I/O 密集型应用程序

        线程池可以处理 I/O 操作,允许系统使用空闲时间进行其他任务处理

        4、Web 应用程序

        线程池可以处理来自 Web 客户端的请求,从而提高了 Web 应用程序的性能

        总之,任何需要处理大量任务或需要同时处理多个请求的应用程序都可以使用线程池来提高性能和效率。

四、设计思想

        设计某个东西一定要有个目标,这里是要求设计一个线程池,首先要明白线程池的作用,然后根据其作用来展开设计。线程池是通过让线程的数量进行动态的变化来及时处理接受的任务,所以核心就是线程和任务。可以将问题转换为如何组织线程和组织任务?借鉴别人的一幅图

         1、如何组织线程

                从上图可以看出,线程被创建出来之后,都处于睡眠态,它们实际上是进入了条件变量的等待队列中,而任务都被放入一个链表,被互斥锁保护起来

                线程的生命周期如下:(这里省略程序执行的流程图)

                (1)、线程被创建

                (2)、预防死锁,准备好退出处理函数,防止在持有一把锁的状态中死去

                (3)、尝试持有互斥锁(等待任务)

                (4)、判断是否有任务,若无则进入条件变量等待队列睡眠,若有则进入第(5)步

                (5)、从任务链表中取得一个任务

                (6)、释放互斥锁

                (7)、弹出退出处理函数,避免占用内存

                (8)、执行任务

                (9)、执行任务完成后重新回到第(2)步

                线程的生命周期其实就是线程要做的事情,把上面的第(2)步到第(8)步写成一个线程的例程函数。

         2、如何组织任务

                所谓的任务就是一个函数,回想一下创建一条线程时,是不是要指定一个线程例程函数。这里的线程池做法是将函数(准确的说是函数指针)及其参数存入一个任务节点,并将节点链接成一个链表。所以现在的问题变成如何设计一个任务链表?

                一个链表的操作有:创建节点,增加节点,删除节点,查询节点这几步。由于这里是任务节点,就不需要查询了,当然也可以查询。所以现在的问题转换为任务节点如何设计?

                任务节点的数据域主要有函数指针和函数参数,指针域就是一个简单任务结点指针,也可以是双向循环链表。

                设计分析到这里,基本上有了框架了,前面买了这么多关子,下面就来个重头戏。其实前面主要是想描述一种设计思想,遇到新的东西如何设计它?首先要明白设计的目的和作用,然后找出核心点,最后就是把核心点进行不断的分析推理,一步步转换为最基本的问题。学过项目管理的同学应该听过在范围管理里有个叫工作分解结构的东西,这里跟那个差不多。

五、案例

        实现一个线程池,并完成线程池的基本操作演示

        thread_pool.h(线程池头文件)

#ifndef _THREAD_POOL_H
#define _THREAD_POOL_H#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>#define MAX_WAITING_TASKS	1000    // 最大的等待任务数量
#define MAX_ACTIVE_THREADS	20      // 最多的活跃线程数量// 任务节点
typedef struct task 
{void *(*do_task) (void *arg);   // 函数指针void *arg;      // 函数参数struct task *next;  // 指向下一个任务节点
}task;// 线程池的管理节点
typedef struct thread_pool
{pthread_mutex_t task_list_lock;   // 任务列表互斥锁,保证同一个时间只有一条线程进行访问pthread_cond_t  task_list_cond;   // 任务列表条件变量,保证没有任务时,线程进入睡眠task *task_list;    // 任务列表pthread_t *tids;     // 存放线程号的数组int shutdown;  // 线程池销毁的开关unsigned int max_waiting_tasks;  // 最大等待的任务数量unsigned int waiting_tasks;      // 正在等待被处理的任务数量unsigned int active_threads;     // 正在活跃的线程数}thread_pool;// 初始化线程池
int init_pool(thread_pool *pool, unsigned int threads_number);// 往线程池中添加任务
int add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg);// 往线程池中添加线程
int add_thread(thread_pool *pool, unsigned int additional_threads);// 从线程池中删除线程
int remove_thread(thread_pool *pool, unsigned int removing_threads);// 查询线程池中线程数量
int total_thread(thread_pool *pool);// 销毁线程池
int destroy_pool(thread_pool *pool);// 线程的例程函数
void *routine(void *arg);#endif // _THREAD_POOL_H

        thread_pool.c(线程池实现文件) 

#include "thread_pool.h"// 初始化线程池
int init_pool(thread_pool *pool, unsigned int threads_number)
{if(threads_number < 1){printf("warning: threads_number must bigger than 0\n");return -1;}if(threads_number > MAX_ACTIVE_THREADS){printf("warning: threads number is bigger than MAX_ACTIVE_THREADS(%u)\n", MAX_ACTIVE_THREADS);return -1;}pthread_mutex_init(&pool->task_list_lock, NULL); // 初始化互斥锁pthread_cond_init(&pool->task_list_cond, NULL);  // 初始化条件变量pool->task_list = calloc(1, sizeof(task));  // 申请一个任务头节点if(!pool->task_list){printf("error: task_list calloc fail\n");return -1;}pool->task_list->next = NULL;   // 让任务头节点指向NULLpool->tids = calloc(MAX_ACTIVE_THREADS, sizeof(pthread_t)); // 申请堆数组用来存放线程号if(!pool->tids){printf("error: tids calloc fail\n");return -1;}pool->shutdown = 0; // 关闭线程池销毁开发pool->waiting_tasks = 0;    // 当前等待任务为0pool->max_waiting_tasks = MAX_WAITING_TASKS;    // 初始化最大等待任务数量pool->active_threads = threads_number;  // 初始化活跃的线程数// 根据活跃的线程数,创建对应数量的线程for(int i = 0; i < pool->active_threads; i++){// 需要判断线程是否创建失败// 线程号用线程号数组,线程属性设置为默认的,例程函数是routine,函数参数是线程池errno = pthread_create(&pool->tids[i], NULL, routine,(void*)pool);if(errno != 0){perror("error: pthread_create fail");return -1;}}return 1;
}// 往线程池中添加任务
int add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg)
{if(!pool){printf("warning: thread_pool is null\n");return -1;}// 如果当前等待执行的任务数超过最大等待的任务数量,则退出if(pool->waiting_tasks >= pool->max_waiting_tasks){printf("warning: task_list is full, too many list\n");return -1;}// 尝试给新的任务节点申请空间task *new_task = (task*)malloc(sizeof(task));if(!new_task){printf("error: task malloc fail\n");return -1;}// 初始化任务节点new_task->do_task = do_task;new_task->arg = arg;new_task->next = NULL;// 把任务节点添加到任务列表最后面// 1、先找到任务列表末尾task *tmp = NULL;for(tmp = pool->task_list; tmp->next; tmp = tmp->next);// 2、上锁pthread_mutex_lock(&pool->task_list_lock);// 3、添加新任务到任务列表末尾,同时当前等待任务数+1tmp->next = new_task;pool->waiting_tasks++;// 4、解锁pthread_mutex_unlock(&pool->task_list_lock);// 唤醒一个正在条件变量中睡眠等待的线程,取执行任务pthread_cond_signal(&pool->task_list_cond);return 1;
}// 往线程池中添加线程,返回实际添加的线程数量
int add_thread(thread_pool *pool, unsigned int additional_threads)
{if(!pool){printf("warning: thread_pool is null\n");return -1;}if(additional_threads == 0){return 0;}// 期望活跃的线程数 = 目前活跃的线程数 + 期望添加的线程数unsigned int total_threads = pool->active_threads + additional_threads;// 如果超过最大的活跃线程数就打印警告if(total_threads > MAX_ACTIVE_THREADS){printf("warning: add too many threads\n");}int actual_add_threads = 0;for(int i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS; i++){// 需要判断线程是否创建失败// 线程号用线程号数组,线程属性设置为默认的,例程函数是routine,函数参数是线程池errno = pthread_create(&pool->tids[i], NULL, routine,(void*)pool);if(errno != 0){perror("error: pthread_create fail");// 如果一个都没有创建成功,就直接返回if(actual_add_threads == 0){return -1;}// 如果成功创建了多个线程,但是本次创建失败,就跳出循环break;}else{actual_add_threads++;   // 线程创建成功就+1}}// 更新线程池中活跃的线程数pool->active_threads += actual_add_threads;return actual_add_threads;  // 返回实际添加的线程
}// 从线程池中删除线程,返回实际删除线程的数量
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{if(!pool){printf("warning: thread_pool is null\n");return -1;}if(removing_threads == 0){return pool->active_threads;}// 如果要删除的线程数大于线程池中活跃的线程数,则打印警告if(removing_threads >= pool->active_threads){printf("warning: remove too many threads\n");}// 剩余数量 = 活跃数量 - 删除的目标数  int remaining_threads = pool->active_threads - removing_threads;//  目的是为了让线程池中最少保留有一个线程可以用于执行任务remaining_threads = remaining_threads > 0 ? remaining_threads : 1;int actual_remove_threads = 0;;// 循环取消线程直到等于期望线程数for(int i = pool->active_threads-1; i > remaining_threads-1; i--){errno = pthread_cancel(pool->tids[i]);if(errno != 0){printf("[%ld] cancel error: %s\n", pool->tids[i], strerror(errno));break;}else{actual_remove_threads++;}}// 更新线程池中活跃的线程数量pool->active_threads -= actual_remove_threads;return actual_remove_threads;   // 返回实际删除的线程
}// 查询线程池中线程数量
int total_thread(thread_pool *pool)
{return pool->active_threads;
}// 销毁线程池
int destroy_pool(thread_pool *pool)
{if(!pool){printf("warning: thread_pool is null\n");return -1;}pool->shutdown = 1; // 启动线程池销毁开关pthread_cond_broadcast(&pool->task_list_cond);    // 唤醒所有在线程池中的线程// 循环等待所有线程退出for(int i = 0; i < pool->active_threads; i++){errno = pthread_join(pool->tids[i], NULL);if(errno != 0){printf("join tids[%d] error: %s\n", i, strerror(errno));}else{printf("[%ld] is joined\n", pool->tids[i]);}}// 头删法,从头一个个的删除任务节点for(task *p = pool->task_list->next; p; p = pool->task_list){pool->task_list->next = p->next;    // 修改首元节点free(p);}free(pool->task_list);free(pool->tids);free(pool);return 1;
}// 线程的取消函数
void pthread_cancel_handler(void *arg)
{printf("[%ld] is cancel\n", pthread_self());pthread_mutex_unlock((pthread_mutex_t*)arg);    // 解锁
}// 线程的例程函数
void *routine(void *arg)
{task *task_p;   // 任务指针,用来执行将要执行的任务thread_pool *pool = (thread_pool*)arg;while(1){pthread_cleanup_push(pthread_cancel_handler, (void*)&pool->task_list_lock);// 尝试持有互斥锁pthread_mutex_lock(&pool->task_list_lock);// 判断是否有任务,没有则进入睡眠// 1、没有任务,线程池销毁开关断开,则进入条件变量等待队列while(!pool->waiting_tasks && !pool->shutdown){// 当条件不满足时,会先自动解锁pool->lock,然后等待到条件满足后,会自动上锁pool->lockpthread_cond_wait(&pool->task_list_cond, &pool->task_list_lock);}// 2、没有任务,线程池销毁开发闭合,则先解锁,然后退出if(!pool->waiting_tasks && pool->shutdown){pthread_mutex_unlock(&pool->task_list_lock);  // 需要解锁pthread_exit(NULL);}// 3、有任务,线程池销毁开关断开,则取一个任务task_p = pool->task_list->next;pool->task_list->next = task_p->next; // 弹出第一任务节点 pool->waiting_tasks--;  // 当前等待的任务数量-1,pthread_mutex_unlock(&pool->task_list_lock);  // 解锁// 弹出压入的线程取消函数,运行到这里不执行,但是当线程在前面被意外取消或中断会执行pthread_cleanup_pop(0); // 为了防止死锁,执行任务期间不接受线程取消请求pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);(task_p->do_task)(task_p->arg); // 通过函数指针的方式执行任务// 执行完任务后,接受线程取消请求pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);free(task_p);   // 删除任务节点}pthread_exit(NULL);
}

        main.c(线程池操作演示文件) 

// 线程池的测试案例#include <stdio.h>
#include <unistd.h>
#include "thread_pool.h"void *mytask(void *arg)
{	int n = rand()%10;printf("[%ld][%s] ==> job will be done in %d sec...\n", pthread_self(), __FUNCTION__, n);sleep(n);printf("[%ld][%s] ==> job done!\n", pthread_self(), __FUNCTION__);
}void *func(void *arg )
{printf("this is a test by [%s]\n", (char*)arg);sleep(1);printf("test finish...\n");
}void *count_time(void *arg)
{int i = 0;while(1){sleep(1);printf("sec: %d\n", ++i);}
}int main(void)
{pthread_t a;pthread_create(&a, NULL, count_time, NULL);// 1、初始化线程池thread_pool *pool = malloc(sizeof(thread_pool));init_pool(pool, 2);// 2、投放任务printf("throwing 3 tasks...\n");add_task(pool, mytask, NULL);add_task(pool, mytask, NULL);add_task(pool, mytask, NULL);// 3、查看当前线程池中的线程数量printf("current thread number: %d\n", total_thread(pool));sleep(9);// 4、再次投放任务printf("throwing another 2 tasks...\n");add_task(pool, mytask, NULL);add_task(pool, mytask, NULL);add_task(pool, func, (void *)"Great Macro");// 5、添加2条线程printf("try add 2 threads, actual add %d\n", add_thread(pool, 2));printf("current thread number: %d\n", total_thread(pool));sleep(5);// 6、删除3条线程printf("try remove 3 threads, actual remove: %d\n", remove_thread(pool, 3));printf("current thread number: %d\n", total_thread(pool));sleep(5);// 7、 销毁线程池printf("destroy thread pool\n");destroy_pool(pool);return 0;
}

        注:编译时,把线程池文件和测试文件放在同一个工作目录下。

        实际使用时,只需要把thread_pool.h和thread_pool.c拷贝到自己的工程目录下,然后根据规则操作线程池。

六、总结

        线程池是许多线程的集合,它不是线程组。线程池适用于任何需要处理大量任务或需要同时处理多个请求的应用程序的场景,可以提供性能和效率。

        至此,Linux系统编程系列,16篇完结撒花,历时5天,这年中秋国庆没有假放!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/148834.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IPsec_SSL VPN身份鉴别过程简要

一、IPsec VPN身份鉴别&#xff08;参考国密标准《GMT 0022-2014 IPsec VPN技术规范》&#xff09; IKE第一阶段&#xff08;主模式&#xff09; “消息2”由响应方发出&#xff0c;消息中具体包含一个SA载荷&#xff08;确认所接受的SA提议&#xff09;、响应方的签名证书和…

Jmeter排查正则表达式提取器未生效问题

今天在使用Jmeter的时候遇到一个很简单的问题&#xff0c;使用正则表达式提取token一直未生效&#xff0c;原因是正则表达式中多了一个空格。虽然问题很简单&#xff0c;但是觉得排查问题的方法很普适&#xff0c;所以记录下&#xff0c;也希望能够给遇到问题的大家一个参考。 …

制作 3 档可调灯程序编写

PWM 0~255 可以将数据映射到0 75 150 225 尽可能均匀电压间隔

很普通的四非生,保研破局经验贴

推免之路 个人情况简介夏令营深圳大学情况机试面试结果 预推免湖南师范大学面试结果 安徽大学面试结果 北京科技大学笔试面试结果 合肥工业大学南京航空航天大学面试结果 暨南大学东北大学 最终结果一些建议写在后面 个人情况简介 教育水平&#xff1a;某中医药院校的医学信息…

计算机网络(第8版)-第4章 网络层

4.1 网络层的几个重要概念 4.1.1 网络层提供的两种服务 如果主机&#xff08;即端系统&#xff09;进程之间需要进行可靠的通信&#xff0c;那么就由主机中的运输层负责&#xff08;包括差错处理、流量控制等&#xff09;。 4.1.2 网络层的两个层面 4.2 网际协议 IP 图4-4 网…

PyTorch实例:简单线性回归的训练和反向传播解析

文章目录 &#x1f966;引言&#x1f966;什么是反向传播&#xff1f;&#x1f966;反向传播的实现&#xff08;代码&#xff09;&#x1f966;反向传播在深度学习中的应用&#x1f966;链式求导法则&#x1f966;总结 &#x1f966;引言 在神经网络中&#xff0c;反向传播算法…

【Java 进阶篇】JDBC 数据库连接池 C3P0 详解

数据库连接池是数据库编程中常用的一种技术&#xff0c;它可以有效地管理数据库连接&#xff0c;提高数据库访问的性能和效率。在 Java 编程中&#xff0c;有多种数据库连接池可供选择&#xff0c;其中之一就是 C3P0。本文将详细介绍 C3P0 数据库连接池的使用&#xff0c;包括原…

linux内核分析:网络协议栈

从本质上来讲,所谓的建立连接,其实是为了在客户端和服务端维护连接,而建立一定的数据结构来维护双方交互的状态,并用这样的数据结构来保证面向连接的特性。TCP 无法左右中间的任何通路,也没有什么虚拟的连接,中间的通路根本意识不到两端使用了 TCP 还是 UDP。 所谓的连接…

redis持久化与调优

一 、Redis 高可用&#xff1a; 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999%等等&#xff09;。但是在Redis语境中&#xff0c;高可用的含义似乎要宽泛一些&#x…

OpenCV利用Camshift实现目标追踪

目录 原理 做法 代码实现 结果展示 原理 做法 代码实现 import numpy as np import cv2 as cv# 读取视频 cap cv.VideoCapture(video.mp4)# 检查视频是否成功打开 if not cap.isOpened():print("Error: Cannot open video file.")exit()# 获取第一帧图像&#x…

【赠书活动第3期】《构建新型网络形态下的网络空间安全体系》——用“价值”的视角来看安全

目录 一、内容简介二、读者受众三、图书目录四、编辑推荐五、获奖名单 一、内容简介 经过30多年的发展&#xff0c;安全已经深入到信息化的方方面面&#xff0c;形成了一个庞大的产业和复杂的理论、技术和产品体系。 因此&#xff0c;需要站在网络空间的高度看待安全与网络的…

UE5报错及解决办法

1、编译报错&#xff0c;内容如下&#xff1a; Unable to build while Live Coding is active. Exit the editor and game, or press CtrlAltF11 if iterating on code in the editor or game 解决办法 取消Enable Live Coding勾选

企业部署,springboot+vue+vue,Linux上部署mysql与redis,docker中部署nginx,jenkins。完整详细。

企业项目部署全流程笔记 前言 涉及&#xff1a;Linux服务器&#xff0c;docker&#xff0c;Jenkins&#xff0c;nginx&#xff0c;springoot&#xff0c;vue&#xff0c;mysql&#xff0c;redis&#xff0c;git&#xff0c; docker生成容器类型&#xff1a;MySql&#xff0c…

string类的模拟实现(万字讲解超详细)

目录 前言 1.命名空间的使用 2.string的成员变量 3.构造函数 4.析构函数 5.拷贝构造 5.1 swap交换函数的实现 6.赋值运算符重载 7.迭代器部分 8.数据容量控制 8.1 size和capacity 8.2 empty 9.数据修改部分 9.1 push_back 9.2 append添加字符串 9.3 运算符重载…

[学习笔记]ARXML - Data Format

参考AUTOSAR文档&#xff1a; https://www.autosar.org/fileadmin/standards/R22-11/FO/AUTOSAR_TPS_ARXMLSerializationRules.pdfhttps://www.autosar.org/fileadmin/standards/R22-11/FO/AUTOSAR_TPS_ARXMLSerializationRules.pdf 编码 arxml只允许使用UTF-8编码&#xff…

小谈设计模式(19)—备忘录模式

小谈设计模式&#xff08;19&#xff09;—备忘录模式 专栏介绍专栏地址专栏介绍 备忘录模式主要角色发起人&#xff08;Originator&#xff09;备忘录&#xff08;Memento&#xff09;管理者&#xff08;Caretaker&#xff09; 应用场景结构实现步骤Java程序实现首先&#xff…

VC++创建windows服务程序

目录 1.关于windows标准可执行程序和服务程序 2.服务相关整理 2.1 VC编写服务 2.2 服务注册 2.3 服务卸载 2.4 启动服务 2.5 关闭服务 2.6 sc命令 2.7 查看服务 3.标准程序 3.1 后台方式运行标准程序 3.2 查找进程 3.3 终止进程 以前经常在Linux下编写服务器程序…

小程序中如何开启分销

小程序共享微信生态&#xff0c;商家可以通过小程序来快速扩大自己的销售渠道&#xff0c;其中一个非常受重要的功能就是分销。通过开启分销功能&#xff0c;商家可以让更多的人参与到销售中来&#xff0c;从而提高销售额。那么&#xff0c;在小程序中如何开启设置分销呢&#…

Qt model/view 理解01

在 Qt 中对数据处理主要有两种方式&#xff1a;1&#xff09;直接对包含数据的的数据项 item 进行操作&#xff0c;这种方法简单、易操作&#xff0c;现实方式单一的缺点&#xff0c;特别是对于大数据或在不同位置重复出现的数据必须依次对其进行操作&#xff0c;如果现实方式改…

1802_在Linux系统上开发ARM单机片机嵌入式软件

全部学习汇总&#xff1a; GreyZhang/little_bits_of_linux: My notes on the trip of learning linux. (github.com) 1. 在Linux上也有嵌入式的开发环境&#xff0c;或许还有很多。不过&#xff0c;我现在接触到的大部分还是Windows居多。这一份文件介绍的是一个mbed platform…