2008 iis 网站,东莞微网站建设公司哪家好,国外网站上不去 dns,平面设计公司排名前十(꒪ꇴ꒪ )#xff0c;Hello我是祐言QAQ我的博客主页#xff1a;C/C语言#xff0c;数据结构#xff0c;Linux基础#xff0c;ARM开发板#xff0c;网络编程等领域UP#x1f30d;快上#x1f698;#xff0c;一起学习#xff0c;让我们成为一个强大的攻城狮#xff0…(꒪ꇴ꒪ )Hello我是祐言QAQ我的博客主页C/C语言数据结构Linux基础ARM开发板网络编程等领域UP快上一起学习让我们成为一个强大的攻城狮送给自己和读者的一句鸡汤集中起来的意志可以击穿顽石!作者水平很有限如果发现错误请在评论区指正感谢 线程池Thread Pool是一种并发编程的设计模式它用于管理和复用线程以便更有效地处理并发任务。线程池的主要目标是降低线程的创建和销毁成本提高系统的性能和资源利用率。
一、API接口 APIApplication Programming Interface接口是一组定义了软件组件之间如何互相通信和交互的规范和协议。API允许不同的软件模块、应用程序或系统之间共享功能和数据从而实现各种复杂的任务和功能。 我们在学习线程池之前要明白线程池本身不是一个API接口而是一种用于管理和执行任务的并发编程模型。然而线程池通常会被包装在API接口中以便其他开发人员可以更容易地使用它来执行并发任务。 API接口通常定义了一组可用于与软件组件、服务或库进行交互的方法和函数。在这种情况下如果创建了一个包含线程池的库并提供了一组方法或函数来操作和管理线程池那么这些方法或函数可以被视为API接口的一部分。 例如可以设计一个具有以下功能的线程池API接口 init_pool初始化线程池add_task向线程池添加任务add_thread添加新的工作线程remove_thread从线程池中删除工作线程destroy_pool销毁线程池 这些方法将构成线程池API接口的一部分其他开发人员可以使用这些方法来实现并发任务执行而无需了解线程池的内部工作原理。 所以线程池可以成为一个可用于构建API接口的组件以简化并发编程任务的处理这就是我们了解AIP接口的目的当然知道这些还不足以写出一个线程池应用程序我们还需先了解一下有关线程池的结构体。
二、线程池相关的结构体 线程池相关的结构体在线程池的设计中扮演着重要角色用于管理线程池的状态、任务队列等信息。
1.线程池结构体Thread Pool 线程池结构体通常包含了线程池的各种属性和信息用于管理线程池的整体状态。以下是一般线程池结构体的示例
typedef struct ThreadPool {pthread_mutex_t lock; // 互斥锁保护线程池内部数据pthread_cond_t cond; // 条件变量用于线程之间的同步bool shutdown; // 线程池销毁标志struct Task *task_list; // 任务链队列pthread_t *tids; // 存储线程ID的数组unsigned int waiting_tasks; // 等待执行的任务数量unsigned int active_threads; // 活跃线程数量
} ThreadPool;lock和cond是用于线程同步的互斥锁和条件变量shutdown标志用于指示线程池是否正在销毁task_list是一个任务链队列存储待执行的任务tids是存储线程ID的数组waiting_tasks记录等待执行的任务数量active_threads表示当前活跃线程数量。
2.任务结构体Task 任务结构体表示线程池中的任务包含了任务函数指针和参数。以下是一个示例
//任务结构体
struct task
{void *(*task)(void *arg); //返回值为void *的函数指针参数列表void *arg,表示任务的地址void *arg; //表示任务需要的参数struct task *next; //表示下一个任务的地址
};
task 是指向任务函数的指针任务函数接受一个void *参数arg 是传递给任务函数的参数next 是一个指向下一个任务的指针用于构建任务队列。 这些结构体协同工作帮助线程池管理任务的执行和线程的管理。线程池结构体用于维护线程池的状态任务结构体用于表示具体的任务。线程池中的线程会不断从任务队列中取出任务并执行同时线程池负责管理线程的生命周期。
三、线程池 线程池Thread Pool是一种并发编程的设计模式它用于管理和复用线程以便更有效地处理并发任务。线程池的主要目标是降低线程的创建和销毁成本提高系统的性能和资源利用率。
1.基本原理 1线程复用 线程池在启动时创建一组线程这些线程一直保持活动状态可用于处理任务。线程复用消除了频繁创建和销毁线程的开销这也是为什么我们要学习线程池的原因。 2任务队列 线程池通常包括一个任务队列用于存储等待执行的任务条件变量。当任务到达时线程池将任务放入队列并从池中的空闲线程中选择一个来执行任务。 3线程调度 线程池负责调度任务并分配给空闲线程。一旦任务完成线程将返回池中等待下一个任务。 4线程池大小控制 线程池的大小通常是有限的可以根据系统资源和性能需求进行配置。这有助于避免创建过多线程从而导致资源耗尽和性能下降也就是做到合理利用资源。
2.组成部分 1线程池管理器Thread Pool Manager 负责创建、管理和监控线程池的核心组件。它维护线程池的状态包括活动线程数、等待任务数等。 2任务队列Task Queue 用于存储等待执行的任务。任务可以是函数、方法或对象线程池从队列中取出任务并将其分配给可用线程。 3工作线程Worker Threads 线程池中的线程用于执行任务。这些线程在初始化时启动并在完成任务后返回池中以供重用。 4任务接口Task Interface 描述任务的接口或抽象类通常包括一个run方法或函数线程池根据此接口来执行任务。
四、C语言实现线程池
1. 初始化线程池 用于初始化线程池设置线程池的初始状态。 原型 bool init_pool(thread_pool * pool, unsigned int threads_number); 参数 pool 线程池结构体指针用于表示要初始化的线程池 threads_number 指定线程池中的初始线程数量 // 初始化线程池结构体里面的成员根据传入的线程个数创建线程
bool init_pool(thread_pool *pool, unsigned int threads_number)
{//初始化互斥锁pthread_mutex_init(pool-lock, NULL);//初始化条件变量pthread_cond_init(pool-cond, NULL);// 关闭销毁线程池标识pool-shutdown false;// 任务队列头结点pool-task_list malloc(sizeof(struct task)); // 线程ID的指针申请空间pool-tids malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);// 判断任务队列头结点指针跟线程ID的指针是否申请成功if(pool-task_list NULL || pool-tids NULL){perror(allocate memory error);return false;}//将任务链式队列的下一个节点的地址初始化pool-task_list-next NULL;//初始化任务个数0个pool-waiting_tasks 0;//初始化活跃线程个数为传入的threads_number个pool-active_threads threads_number;int i;// 循环创建指定数目线程for(i 0; ipool-active_threads; i) {//调用pthread_create函数创建线程线程ID存放在pool-tids的数组里面if(pthread_create(((pool-tids)[i]), NULL, routine, (void *)pool) ! 0){perror(create threads error);return false;}}return true;
}
2. 添加任务 将一个任务添加到线程池的任务队列中等待线程池的线程执行。 原型 bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void * arg); 参数 pool 线程池结构体指针表示要添加任务的线程池 do_task 任务函数的指针表示要执行的任务 arg 任务函数需要的参数可以是任何类型的数据 // 往链式任务队列添加任务单个唤醒线程去执行任务
bool add_task(thread_pool *pool, void *(*task)(void *arg), void *arg)
{// 新任务节点struct task *new_task malloc(sizeof(struct task));//新任务节点创建失败if(new_task NULL){perror(allocate memory error);return false;}//新任务节点的函数指针做初始化new_task-task task;//新任务节点的函数指针需要的参数做初始化new_task-arg arg;//新任务节点的下一个节点的地址初始化为NULLnew_task-next NULL;// 访问任务队列前获取互斥锁此处无需注册取消处理例程pthread_mutex_lock(pool-lock);//如果 任务链队列里面任务个数 大于等于 最大任务个数if(pool-waiting_tasks MAX_WAITING_TASKS){//解锁pthread_mutex_unlock(pool-lock);fprintf(stderr, too many tasks.\n);free(new_task);return false;}//拿链式队列的头节点指针struct task *tmp pool-task_list;//循环遍历找到最后一个节点的地址while(tmp-next ! NULL){tmp tmp-next;}// 添加新的任务节点tmp-next new_task;// 等待任务个数1pool-waiting_tasks;// 释放互斥锁pthread_mutex_unlock(pool-lock);// 并唤醒其中一个阻塞在条件变量上的线程pthread_cond_signal(pool-cond);return true;
}
3. 添加活跃线程 向线程池中添加额外的活跃线程增加线程池的处理能力。 原型 int add_thread(thread_pool *pool, unsigned int additional_threads); 参数 pool 线程池结构体指针表示要添加线程的线程池 additional_threads 指定要添加的额外线程数量 // 根据传入的数量创建线程
int add_thread(thread_pool *pool, unsigned additional_threads)
{//添加活跃线程个数为0不需要往后执行了if(additional_threads 0){return 0;}// 定义一个变量total_threads 当前活跃线程个数 添加活跃线程个数unsigned total_threads pool-active_threads additional_threads;int i, actual_increment 0;// 循环地创建若干指定数目的线程for(i pool-active_threads; i total_threads i MAX_ACTIVE_THREADS; i){if(pthread_create(((pool-tids)[i]), NULL, routine, (void *)pool) ! 0){perror(add threads error);//添加活跃线程个数如果一开始就为0表示一个都没有创建成功if(actual_increment 0){return -1;}break;}actual_increment;}//更新活跃线程个数用创建成功的个数加上之前的个数pool-active_threads actual_increment;return actual_increment;
}
4. 删除活跃线程 从线程池中删除指定数量的活跃线程减少线程池的处理能力。 原型 int remove_thread(thread_pool *pool, unsigned int removing_threads); 参数 pool 线程池结构体指针表示要删除线程的线程池 removing_threads 指定要删除的线程数量 // 根据传入的个数去删除返回删除之后的剩下的活跃线程个数
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{//如果删除的线程个数为0直接返回当前的活跃线程个数if(removing_threads 0){return pool-active_threads;}//定义一个变量删除之后剩下的线程个数 当前活跃线程个数-删除活跃线程个数int remain_threads pool-active_threads - removing_threads;//如果删除之后的线程个数小于0保留一个如果不小于0就有多少保留多少remain_threads remain_threads 0 ? remain_threads : 1;int i; // 循环地取消掉指定数目的线程for(i pool-active_threads-1; iremain_threads-1; i--){errno pthread_cancel(pool-tids[i]);if(errno ! 0){break;}}//如果一个都没有删返回-1if(i pool-active_threads-1){return -1;}else{//更新最大活跃线程个数pool-active_threads i1;return i1;}
}
5. 销毁线程池 销毁线程池释放线程池占用的资源并停止线程池的运行。 原型 bool destroy_pool(thread_pool *pool); 参数 pool 线程池结构体指针表示要销毁的线程池 // 释放资源
bool destroy_pool(thread_pool *pool)
{//线程池销毁标志为真pool-shutdown true;//广播唤醒条件变量等待队列里面的线程pthread_cond_broadcast(pool-cond);int i;for(i0; ipool-active_threads; i){//pthread_join( )指定的线程如果尚在运行那么他将会阻塞等待errno pthread_join(pool-tids[i], NULL);if(errno ! 0){printf(join tids[%d] error: %s\n, i, strerror(errno));}else{printf([%u] is joined\n, (unsigned)pool-tids[i]);} }free(pool-task_list);free(pool-tids);free(pool);return true;
}
6.线程任务函数 routine( )函数是线程池中线程的主要工作函数它执行以下任务 ①不断地从任务队列中取出任务 ②执行取出的任务 ③如果任务队列为空且线程池没有被销毁则进入等待状态等待新任务的到来。
//创建线程之后开始去执行任务有任务就执行没有任务线程进入条件变量等待队列等待唤醒执行任务
void *routine(void *arg)
{//接收传入进来的参数poolthread_pool *pool (thread_pool *)arg;//定义任务结构体指针变量struct task *p;while(1){// 访问任务队列前加锁为防止取消后死锁注册处理例程 handlerpthread_cleanup_push(handler, (void *)pool-lock);//加锁访问任务队列pthread_mutex_lock(pool-lock);// 若当前没有任务且线程池关闭标志未关闭则进入条件变量等待队列睡眠等待新任务的到来或线程池关闭while(pool-waiting_tasks 0 !pool-shutdown){//解锁 进入条件变量等待队列睡眠 收到通知的时候加锁pthread_cond_wait(pool-cond, pool-lock);}// 若当前没有任务且线程池关闭标识为真则立即释放互斥锁并退出if(pool-waiting_tasks 0 pool-shutdown true){//解锁pthread_mutex_unlock(pool-lock);//线程退出pthread_exit(NULL);}// 若当前有任务则消费任务队列中的任务// 拿链式队列的下一个节点的地址p pool-task_list-next;//将链式队列当前指向指向下一个pool-task_list-next p-next;//任务个数-1pool-waiting_tasks--;// 释放互斥锁并弹栈 handler但不执行他pthread_mutex_unlock(pool-lock);pthread_cleanup_pop(0);// 执行任务并且在此期间禁止响应取消请求执行期间用pthread_cancel发送取消请求pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);//调用任务结构体里面的函数传参数(p-task)(p-arg); // task(arg)//执行任务完成并且在此期间使能响应取消请求pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);//释放p指向的地址空间free(p);}//线程退出pthread_exit(NULL);
}
7.任务函数 void *mytask(void *arg); 该函数接受一个 void 指针类型的参数 arg并返回一个 void 指针。函数主要用于你想要完成的任务操作这里举例了一个文件复制函数
//任务函数想要完成的操作
void *mytask(void *arg)
{//文件复制int fd1 open(./1.txt, O_RDWR);//int fd1 open(arg-first, O_RDWR);if (fd1 -1){perror(open fd1 error);return NULL;}int fd2 open(./2.txt, O_RDWR|O_CREAT, 0777);//int fd2 open(arg-second, O_RDWR|O_CREAT, 0777);if (fd2 -1){perror(open fd2 error);return NULL;}char buf[1024];int size;while(1){bzero(buf, 1024);size read(fd1, buf, 1024);if (size0){break;}write(fd2, buf, size);}close(fd1);close(fd2);return NULL;
} 这里我们创建了五条线程的一个线程池来复制一个拥有50个文件大小为45MB的大文件可以看到用时4.433s然而如果采用单线程处理的话至少时间在8s左右。当然具体情况因个人电脑配置不同而异。 五、总结 让我们用一个类比来解释一下线程池每辆出租车就是一个线程。它的工作是执行各种任务就像司机可以运送乘客到不同的目的地一样。而线程池就像是一家出租公司。这家公司拥有多组出租车线程并根据需要分配任务给这些出租车。公司负责管理、维护和监控这些出租车以确保它们随时可以为乘客提供服务。任务工作就像是乘客需要到达的目的地。这可以是任何需要执行的工作例如计算、数据处理、文件上传等等。线程池接受这些任务并将它们分配给可用的出租车线程从而高效的完成他们。
1.优点
资源控制 线程池可以限制同时运行的线程数量有效控制系统资源的使用。性能提升 通过减少线程的创建和销毁线程池可以提高应用程序的性能减少了线程切换的开销。任务管理 线程池可以管理任务的排队和执行确保任务按照顺序或优先级执行。可伸缩性 线程池可以根据系统负载动态调整线程数量以适应不同的工作负载。 2.适用场景 线程池适用于需要并发执行多个任务的情况特别是在以下情况下使用效果更佳
Web服务器处理多个并发请求。数据库连接池管理多个数据库连接。后台任务处理如日志处理、邮件发送、文件处理等。任何需要并发执行的计算密集型或I/O密集型任务。
3.注意事项 线程池的大小应该根据系统资源和负载需求进行调整过大的线程池可能会消耗大量内存而过小的线程池可能会导致任务排队和性能下降。 线程池中的任务应该是独立的不应该有共享状态或依赖关系以避免竞态条件和死锁。 异常处理对于线程池非常重要确保捕获和处理任务中的异常以避免线程终止和资源泄漏。 更多C/C语言、Linux系统、数据结构和ARM板实战相关文章关注专栏 手撕C语言 玩转linux 脚踢数据结构 系统、网络编程 探索C 6818ARM开发板实战
写在最后
今天的分享就到这啦~觉得博主写的还不错的烦劳 一键三连喔~感谢关注