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

news2024/11/18 13:54:47

上一篇中讲解了在进程池文件传输的过程如何实现零拷贝,具体的方法包括使用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 processnum
    ARGS_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监听Listenfd
    epollAddReadEvent(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.coloradmin.cn/o/1955581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

超越基础功能:项目进度管理工具深度评测

国内外主流的10款项目进度管理网站对比&#xff1a;PingCode、Worktile、滴答清单&#xff08;TickTick&#xff09;、Todoist、NarTick、Teambition、Monday.com、Asana、ClickUp、Trello。 在选择合适的项目进度管理工具时&#xff0c;许多项目经理面临着如何找到既能满足团队…

二十二、作业

目录 1.求代码结果 2.求代码结果 3.使用指针打印数组内容 4.字符串逆序 5.计算求和 6.打印水仙花数 7.打印菱形 8.喝汽水问题 1.求代码结果 输出为00345 2.求代码结果 任何一个变量/表达式&#xff0c;都有2个属性&#xff0c;值属性和类型属性 int a 3&#xff1b;…

Python及Jupyter-Notebook安装

来源&#xff1a; “码农不会写诗”公众号 链接&#xff1a;Python及Jupyter-Notebook安装 文章目录 01 Python安装1.1 下载安装包1.2 双击安装包&#xff0c;开始安装1.3 选择安装配置1.4 选择需要安装的Optional Feature&#xff0c;点击Next1.5 选择需要安装的Advanced Feat…

matplotlib的科研绘图辅助

matplotlib的科研绘图辅助 趁着暑假&#xff0c;与和鲸科技合作了一个python绘图的教程&#xff0c;作为暑期夏令营的一小部分&#xff0c;主要内容是介绍如何使用matplotlib、pandas、seaborn和plotnine进行医学科研绘图&#xff0c;感兴趣的可以通过如下地址进行访问&#x…

Unity XR Interaction Toolkit设置或监听手柄按键事件(三)

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神不吝指教&#xff01; 文章目录 前言一、XRI Default Input Actions1.导入官方案例2.设置控制器绑定&#xff0c;如手柄、主/辅助按钮、操纵杆等1.要设置控制器绑定&#xff0c;如左右手 手柄、主/辅助按钮、操纵杆等…

添加sidecar容器并输出日志

添加一个sidecar容器(使用busybox 镜像)到已有的pod 11-factor-app中,确保sidecar容器能够输出/var/log/11-factor-app.log的信息,使用volume挂载/var/log目录,确保sidecar能访问11-factor-app.log 文件 # 准备工作 创建一个 pod 11-factor-appapiVersion: v1 kind: Pod metada…

【研路导航】保研英语面试高分攻略,助你一路过关斩将

面试攻略之 千锤百炼英语口语 写在前面 在保研面试中&#xff0c;英语口语往往是让许多同学感到头疼的一部分。如何在面试中展现出自信和流利的英语表达能力&#xff0c;是我们今天要探讨的主题。以下是一些有效的英语口语练习方法和常见题型解析&#xff0c;帮助你在保研面试…

GUI - Tkinter - MVC

【python】 property属性详解_python property-CSDN博客Tkinter MVC (pythontutorial.net)GUI架构演进之MVC&#xff08;一&#xff09; - frydsh - 博客园 (cnblogs.com)MVC 模式 | 菜鸟教程 (runoob.com)MVC 架构详解 (freecodecamp.org)Python之MVC - chenbiao - SegmentFau…

灵活数据流处理:NeuronEX 支持 JavaScript 自定义函数

随着数据要素逐渐成为帮助工业企业提升智能化水平的重要助力&#xff0c;如何灵活采集和处理工业数据&#xff0c;并满足用户定制化的数据需求&#xff0c;成为企业数字化建设的焦点之一。 NeuronEX 是一款专为工业场景设计的边缘网关软件&#xff0c;具备工业设备数据采集、工…

@JSONField(format = “yyyyMMddHH“)的作用和使用

JySellerItqrdDataDO对象中的字段为&#xff1a; private Date crdat; 2.数据库中的相应字段为&#xff1a; crdat datetime DEFAULT NULL COMMENT 创建时间,2. 打印出的结果为&#xff1a; “crdat”:“2024072718” 年月日时分秒 3. 可以调整format的格式 4. 这样就把Date类…

信息搜集——小米

小米 主域名&#xff1a;www.miui.com 备案网站&#xff1a;27个 备案APP&#xff1a;21个 备案小程序&#xff1a;13个 备案公众号&#xff1a;23个 备案微博&#xff1a;43个 IP 域名 端口 状态码 Ping 网址 多地ping 网站名称 网址 域名 网站备案/许可证号 公 司名…

手撕数据结构---------顺序表和链表

1.线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构&#xff0c;常⻅的线性表&#xff1a;顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构&#xff0c;也就说是连续的⼀条直…

java实战项目--拼图小游戏(附带全套源代码)

个人主页VON 所属专栏java实战项目游戏参考黑马程序员 一、效果展示 二、功能介绍 游戏中所有的图片以及代码均已打包&#xff0c;玩家直接安装游戏即可&#xff0c;不用idea也可以畅玩。 游戏功能比较单一&#xff0c;只有简单的拼图功能。 a&#xff1a;展示原图重新游戏&a…

初涉JVM

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

whaler_通过镜像导出dockerfile

1、Whaler简介 Whaler:从镜像导出Dockerfile&#xff0c;whaler英文释义捕鲸船。 2、下载安装 # wget -cO /usr/local/bin/whaler https://github.com/P3GLEG/Whaler/releases/download/1.0/Whaler_linux_amd64 3、赋予可执行权限 [rootlocalhost ~]# chmod x /usr/local/…

Android OTA刷机包制作学习笔记

前言 OTA是一个再常见不过的需求&#xff0c;Android提供了recovery用于完成相关操作。 常规OTA包制作有两种&#xff1a; 有项目的完整AOSP源码&#xff0c;可以在成构建产物zip包后利用官方脚本制作。具体参阅&#xff1a;Office OTA假设你没有1的条件那么可以利用官方非A/…

exo-tinggrad 架构解析

目录 exo-tinggrad 架构解析 8B 模型配置 70B 模型配置 exo-tinggrad 架构解析 这个项目目录包含了一系列与Python相关的文件和文件夹,它们共同构成了一个可能的项目或库。这些文件和文件夹按照特定的命名和组织方式被放置在了一起,以便于管理、开发和维护。 tinygrad: 这…

24.7.28(tarjan 割点,割边,多重背包单调队列优化)

星期一&#xff1a; cf round 960 div2 B 简单构造 cf传送门 题意有点绕 思路&#xff1a;开始容易想到 y前和 x后全-1&#xff0c;y到x填1的构造&#xff0c;但对于 5 2 1&#xff0c;1 1 -1 -1 -1有问题&#xff0c;1和5的后缀值都为 -1…

【MySQL进阶之路 | 高级篇】简述Bin Log日志

1. 日志类型 MySQL有不同类型的日志文件&#xff0c;用来存储不同类型的日志&#xff0c;分为二进制日志、错误日志、通用查询日志和慢查询日志&#xff0c;这也是常用的4种。MySQL 8又新增两种支持的日志:中继日志和数据定义语句日志。使用这些日志文件&#xff0c;可以查看M…

树与二叉树【数据结构】

前言 之前我们已经学习过了各种线性的数据结构&#xff0c;顺序表、链表、栈、队列&#xff0c;现在我们一起来了解一下一种非线性的结构----树 1.树的结构和概念 1.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一…