【Linux C | 网络编程】进程池退出的实现详解(五)

上一篇中讲解了在进程池文件传输的过程如何实现零拷贝,具体的方法包括使用mmap,sendfile,splice等等。

【Linux C | 网络编程】进程池零拷贝传输的实现详解(四)

这篇内容主要讲解进程池如何退出。

1.进程池的简单退出

进程池的简单退出要实现功能很简单,就是让父进程收到信号之后,再给每个子进程发送信号使其终止,这种实现方案只需要让父进程在一个目标信号(通常是10 信号 SIGUSR1 )的过程给目标子进程发送信号即可。
在实现的过程需要注意的是 signal 函数和 fork 函数之间调用顺序,因为父进程会修改默认递送行为,而子进程会执行默认行为,所以 fork 应该要在 signal 的前面调用。
processData_t *workerList;//需要改成全局变量
int workerNum;
void sigFunc(int signum){printf("signum = %d\n", signum);for(int i = 0; i < workerNum; ++i){kill(workerList[i].pid,SIGUSR1);}for(int i = 0; i < workerNum; ++i){wait(NULL);}puts("process pool is over!");exit(0);
}
int main(){
//..makeChild(workerList,workerNum);signal(SIGUSR1,sigFunc);//注意fork和signal的顺序
}

2.使用管道通知工作进程终止

采用信号就不可避免要使用全局变量,因为信号处理函数当中只能存储有限的信息,有没有办法避免全局的进程数量和进程数组呢? 一种解决方案就是采取“ 异步拉起同步 的策略:虽然还是需要创建一个管道全局变量,但是该管道只用于处理进程池退出,不涉及其他的进程属性。这个管道的读端需要使用IO多路复用机制管理起来,而当信号产生之后,主进程递送信号的时候会往管道中写入数据,此时可以依靠 epoll 的就绪事件,在事件处理中来完成退出的逻辑。

异步执行与同步执行的区别

  • 异步执行:异步操作是指当一个任务开始执行后,控制流可以继续执行下一个任务,而不需要等待当前任务完成。异步操作通常会通过回调函数、事件驱动机制或者Promise等方式来处理结果或通知任务的完成状态。在异步操作中,调用者通常不会立即等待操作的完成。

  • 同步执行:同步操作是指一个任务开始执行后,调用者会一直等待任务完成,然后才能继续执行下一个任务。同步操作的执行顺序是按照代码的顺序依次执行的,直到当前任务完成。

异步拉起同步的应用场景和解释

异步拉起同步通常用来描述这样一种情况:

  1. 异步任务的启动:首先,某个操作或任务以异步的方式启动,这意味着调用者可以在任务启动后继续执行其他操作,而不必等待任务完成。

  2. 同步任务的结束:随后,在异步任务执行完毕后,系统或程序需要在某个点上进行同步操作,即等待这个异步任务完成并获取其结果,然后才能继续执行依赖于该结果的下一步操作。

进程池退出中的异步拉起同步的具体流程

  1. 异步提交任务:主进程使用进程池异步地提交多个任务给子进程执行。这些任务可以是函数、方法或者其他可调用对象。

  2. 任务执行:进程池管理器负责分配任务给空闲的子进程,并在子进程完成任务后收集返回的结果。

  3. 等待任务完成:一旦主进程不再提交新任务到进程池,它需要等待所有已提交的任务都完成。这时候,主进程通常会发出一个信号或者事件,表示进程池应该开始进入退出状态。

  4. 异步转同步:进程池收到退出信号后,开始等待所有任务完成。此时,进程池中的子进程继续处理它们的任务,主进程则通过某种机制(例如等待事件或轮询任务状态)来异步地等待所有子进程的任务完成。

  5. 进程池退出:一旦所有任务完成,进程池中的子进程会被优雅地退出。主进程也可以在这个时候执行一些清理工作,例如关闭文件、释放资源等。

简单点总结就是:

信号 + 匿名管道  =》 异步拉起同步

1. SIGUSR1  父进程获取信号后,在信号处理函数中,通过匿名管道进入epoll

2. 在epoll循环中,有两种方式让子进程退出     

A. 在父进程中,直接调用kill函数,给子进程发送SIGUSR1信号(粗暴)     

B. 在父进程中,通过sendFd函数,通知子进程退出(温和)在子进程中,通过recvFd函数,获取到退出标志位,然后退出,可以确保每一个任务都能够执行完毕。

粗暴的退出代码:

#include "process_pool.h"//退出使用的匿名管道
int exitPipe[2];void sighandler(int signum)
{printf("signum %d is coming.\n", signum);int one = 1;//父进程收到信号,往管道写端写入数据write(exitPipe[1], &one, sizeof(one));
}int main(int argc, char ** argv)
{//ip port processnumARGS_CHECK(argc, 4);int processNum = atoi(argv[3]);process_data * pProcess = calloc(processNum, sizeof(process_data));//让父子进程都忽略掉SIGPIPE信号//signal(SIGPIPE, SIG_IGN);//创建N个子进程makeChild(pProcess, processNum);//makechild函数之后,都是父进程的操作//在父进程中注册信号处理函数signal(SIGUSR1, sighandler);//只在父进程中创建退出的管道pipe(exitPipe);//创建监听的服务器int listenfd = tcpInit(argv[1], atoi(argv[2]));//创建epoll的实例int epfd = epoll_create1(0);ERROR_CHECK(epfd, -1, "epfd");//epoll监听ListenfdepollAddReadEvent(epfd, listenfd);//epoll监听进程池退出的管道读端exitPipe[0],epollAddReadEvent(epfd, exitPipe[0]);///epoll监听父子进程间通信的管道for(int i = 0; i < processNum; ++i) {epollAddReadEvent(epfd, pProcess[i].pipefd);}//定义保存就绪的文件描述符的数组struct epoll_event eventArr[10] = {0};int nready = 0;while(1){nready = epoll_wait(epfd, eventArr, sizeof(eventArr), -1);for(int i = 0; i < nready; ++i) {int fd = eventArr[i].data.fd; //新客户端到来if(fd == listenfd) {struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int peerfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);ERROR_CHECK(peerfd, -1, "accept");printf("client %s:%d connected.\n",inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));//将peerfd发送给一个空闲的子进程for(int j = 0; j < processNum; ++j) {if(pProcess[j].status == FREE) {sendFd(pProcess[j].pipefd, peerfd);pProcess[j].status = BUSY;break;}}//如果要断开与客户端的连接,这里还得执行一次close(peerfd);} else if(fd == exitPipe[0])  {     //匿名管道读端有数据,说明父进程收到退出的信号,循环给子进程发出退出信号int howmany = 0;read(fd, &howmany, sizeof(howmany));//处理进程池退出的情况//第一种方式: 父进程给子进程发送SIGUSR1信号for(int j = 0; j < processNum; ++j) {kill(pProcess[j].pid, SIGUSR1);}for(int j = 0; j < processNum; ++j) {wait(NULL);}goto end;} else {//管道发生了事件: 子进程已经执行完任务了int howmany = 0;read(fd, &howmany, sizeof(howmany));for(int j = 0; j < processNum; ++j) {if(pProcess[j].pipefd == fd) {pProcess[j].status = FREE;printf("child %d is not busy.\n", pProcess[j].pid);break;}}}}}
end:printf("exit process pool.\n");free(pProcess);close(exitPipe[0]);close(exitPipe[1]);close(listenfd);close(epfd);return 0;
}

3.优雅退出

上述的退出机制存在一个问题,就是即使工作进程正在传输文件中,父进程也会通过信号将其终止。如何实现进程池在退出的时候,子进程要完成传输文件的工作之后才能退出呢?
一种典型的方案是使用 sigprocmask 在文件传输的过程中设置信号屏蔽字,这样可以实现上述的机制。
另一种方案就是调整 sendFd 的设计,每个工作进程在传输完文件之后总是循环地继续下一个事件,而在每个事件处理的开始,工作进程总是会调用 recvFd 来使自己处于阻塞状态直到有事件到达。我们可以对进程池的终止作一些调整:用户发送信号给父进程表明将要退出进程池;随后父进程通过 sendFd给所有的工作进程发送终止的信息,工作进程在完成了一次工作任务了之后就会 recvFd 收到进程池终止的信息,然后工作进程就可以主动退出;随着所有的工作进程终止,父进程亦随后终止,整个进程池就终止了。

int sendFd(int pipeFd, int fdToSend, int exitFlag){struct msghdr hdr;bzero(&hdr,sizeof(struct msghdr));struct iovec iov[1];iov[0].iov_base = &exitFlag;iov[0].iov_len = sizeof(int);hdr.msg_iov = iov;hdr.msg_iovlen = 1;//...
}
int recvFd(int pipeFd, int *pFd, int *exitFlag){struct msghdr hdr;bzero(&hdr,sizeof(struct msghdr));struct iovec iov[1];iov[0].iov_base = exitFlag;iov[0].iov_len = sizeof(int);hdr.msg_iov = iov;hdr.msg_iovlen = 1;//.....
}

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

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

相关文章

聊聊基于Alink库的主成分分析(PCA)

概述 主成分分析&#xff08;Principal Component Analysis&#xff0c;PCA&#xff09;是一种常用的数据降维和特征提取技术&#xff0c;用于将高维数据转换为低维的特征空间。其目标是通过线性变换将原始特征转化为一组新的互相无关的变量&#xff0c;这些新变量称为主成分&…

7月24日JavaSE学习笔记

序列化版本控制 序列化&#xff1a;将内存对象转换成序列&#xff08;流&#xff09;的过程 反序列化&#xff1a;将对象序列读入程序&#xff0c;转换成对象的方式&#xff1b;反序列化的对象是一个新的对象。 serialVersionUID 是一个类的序列化版本号 private static fin…

算法通关:006_1二分查找

二分查找 查找一个数组里面是否存在num主要代码运行结果 详细写法自动生成数组和num&#xff0c;利用对数器查看二分代码是否正确 查找一个数组里面是否存在num 主要代码 /*** Author: ggdpzhk* CreateTime: 2024-07-27*/ public class cg {//二分查找public static boolean …

戴着苹果Vision Pro,如何吃花生米

6月底苹果Vision Pro国内开售&#xff0c;我早早到官网预订了一台。选择必要的配件&#xff0c;输入视力信息&#xff0c;定制符合自己视力的蔡司镜片。确实贵。把主要配件和镜片配齐&#xff0c;要3万6&#xff0c;比Pico、META的眼镜贵一个数量级。 Vision Pro出来后&#x…

C++的STL简介(一)

目录 1.什么是STL 2.STL的版本 3.STL的六大组件 4.string类 4.1为什么学习string类&#xff1f; 4.2string常见接口 4.2.1默认构造 ​编辑 4.2.2析构函数 Element access: 4.2.3 [] 4.2.4迭代器 ​编辑 auto 4.2.4.1 begin和end 4.2.4.2.regin和rend Capacity: 4.2.5…

Q238. 除自身以外数组的乘积

思路 一开始想到的是按位乘 看了题解&#xff0c;思路是存i左边的乘积和 与 i右边的乘积和 代码一&#xff1a; 需要三次循环,需要额外空间 left和right数组 代码&#xff1a; public int[] productExceptSelf(int[] nums) {int[] left new int[nums.length];int[] right …

【爱上C++】list用法详解、模拟实现

文章目录 一&#xff1a;list介绍以及使用1.list介绍2.基本用法①list构造方式②list迭代器的使用③容量④元素访问⑤插入和删除⑥其他操作image.png 3.list与vector对比 二&#xff1a;list模拟实现1.基本框架2.节点结构体模板3.__list_iterator 结构体模板①模板参数说明②构…

基于Xejen框架实现的C# winform鼠标点击器、电脑按键自动点击器的软件开发及介绍

功能演示 文章开始之前&#xff0c;仍然是先来个视频&#xff0c;以便用户知道鼠标连点器的基本功能 软件主界面 多功能鼠标连点器 快速点击&#xff1a; 痕即鼠标点击器可以设定每秒点击次数&#xff0c;让您轻松应对高频点击需求。 切换时长&#xff0c;即每次动作之间的间…

大数据的数据质量有效提升的研究

大数据的数据质量有效提升是一个涉及多个环节和维度的复杂过程。以下是从数据采集、处理、管理到应用等方面&#xff0c;对大数据数据质量有效提升的研究概述&#xff1a; 一、数据采集阶段 明确采集需求&#xff1a;在数据采集前&#xff0c;需明确数据需求&#xff0c;包括…

leetocde662. 二叉树最大宽度,面试必刷题,思路清晰,分点解析,附代码详解带你完全弄懂

leetocde662. 二叉树最大宽度 做此题之前可以先做一下二叉树的层序遍历。具体题目如下&#xff1a; leetcode102二叉树的层序遍历 我也写过题解&#xff0c;可以先看看学习一下&#xff0c;如果会做层序遍历了&#xff0c;那么这题相对来说会简单很多。 具体题目 给你一棵…

把 网页代码 嵌入到 单片机程序中 2 日志2024/7/26

之前不是说把 网页代码 嵌入到 单片机程序中 嘛! 目录 之前不是说把 网页代码 嵌入到 单片机程序中 嘛! 修改vs的tasks.json配置 然后 测试 结果是正常的,可以编译了 但是:当我把我都html代码都写上去之后 还是会报错!!! 内部被检测到了,没辙,只有手动更新了小工具代码 …

Python3网络爬虫开发实战(2)爬虫基础库

文章目录 一、urllib1. urlparse 实现 URL 的识别和分段2. urlunparse 用于构造 URL3. urljoin 用于两个链接的拼接4. urlencode 将 params 字典序列化为 params 字符串5. parse_qs 和 parse_qsl 用于将 params 字符串反序列化为 params 字典或列表6. quote 和 unquote 对 URL的…

JAVAWeb实战(前端篇)

项目实战一 0.项目结构 1.创建vue3项目&#xff0c;并导入所需的依赖 npm install vue-router npm install axios npm install pinia npm install vue 2.定义路由&#xff0c;axios&#xff0c;pinia相关的对象 文件&#xff08;.js&#xff09; 2.1路由(.js) import {cre…

HarmonyOS Next 省市区级联(三级联动)筛选框

效果图 完整代码 实例对象 export class ProvinceBean {id?: stringpid?: stringisSelect?: booleandeep?: objectextName?: stringchildren?: ProvinceBean[] }级联代码 import { MMKV } from tencent/mmkv/src/main/ets/utils/MMKV import { ProvinceBean } from ..…

nodeselector

1.概述 在创建pod资源是&#xff0c;k8s集群系统会给我们将pod资源随机分配到不同服务器上。我们通过配置nodeSelector可以将pod资源指定到拥有某个标签的服务器上 使用nodeselector前我们要先给每个节点打上标签&#xff0c;在编辑pod资源的时候选择该标签 2.示例 给节点打标…

数据科学统计面试问题 -40问

前 40 名数据科学统计面试问题 一、介绍 正如 Josh Wills 曾经说过的那样&#xff0c;“数据科学家是一个比任何程序员都更擅长统计、比任何统计学家都更擅长编程的人”。统计学是数据科学中处理数据及其分析的基本工具。它提供了工具和方法&#xff0c;可帮助数据科学家获得…

初涉JVM

JVM 字节码、类的生命周期、内存区域、垃圾回收 JVM主要功能&#xff1a; 解释运行&#xff08;翻译字节码&#xff09;内存管理&#xff08;GC&#xff09;即使编译&#xff08;Just - In - Time&#xff0c; JIT&#xff09; 将短时间内常使用到的字节码翻译成机器码存储在内…

【Gin】智慧架构的巧妙砌筑:Gin框架中控制反转与依赖注入模式的精华解析与应用实战(下)

【Gin】智慧架构的巧妙砌筑&#xff1a;Gin框架中控制反转与依赖注入模式的精华解析与应用实战(下) 大家好 我是寸铁&#x1f44a; 【Gin】智慧架构的巧妙砌筑&#xff1a;Gin框架中控制反转与依赖注入模式的精华解析与应用实战(下)✨ 喜欢的小伙伴可以点点关注 &#x1f49d; …

uboot的mmc partconf命令

文章目录 命令格式参数解释具体命令解释总结 mmc partconf 是一个用于配置 MMC (MultiMediaCard) 分区的 U-Boot 命令。具体来说&#xff0c;这个命令允许你设置或读取 MMC 卡的分区配置参数。让我们详细解释一下 mmc partconf 0 0 1 0 命令的含义。 命令格式 mmc partconf &…

【网络安全】子域名模糊测试实现RCE

未经许可&#xff0c;不得转载。 文章目录 正文总结 正文 在之前测试一个私人项目时&#xff0c;我报告了admin.Target.com上的Auth Bypass漏洞&#xff0c;这将导致SQLI&RCE &#xff0c;该漏洞在报告后仅一天就被修复。 现在重拾该应用程序&#xff0c;对子域进行模糊测…