当前位置: 首页 > news >正文

深入理解进程等待:wait的简化与waitpid的灵活性

       进程等待是操作系统中进程管理的一个重要概念,指的是一个进程暂停执行,直到某个特定条件满足后再继续执行的过程。

目录

一、基本概念

二、进程等待的必要性

三、进程等待方法

1、wait方法

2、waitpid方法

3、注意事项

4、核心区别

5、waitpid 的 PID 参数选项

6、waitpid 的 options 选项

7、联系

四、获取子进程状态

Core Dump(了解即可)

五、阻塞与非阻塞等待

1、进程的阻塞等待方式

2、进程非阻塞等待实现


一、基本概念

进程等待通常发生在以下几种情况:

  1. 等待I/O操作完成:如磁盘读写、网络数据传输等

  2. 等待子进程结束:父进程等待子进程执行完毕

  3. 等待资源可用:如等待锁、信号量等同步机制

  4. 等待时间条件:如sleep()函数调用


二、进程等待的必要性

  • 当子进程退出时,若父进程未读取其退出信息,子进程将转为僵尸状态,导致内存泄漏。
  • 僵尸进程无法通过kill -9命令清除,因为已终止的进程无法被再次终止。
  • 父进程作为子进程的直接管理者,需要了解其派发任务的完成情况。
  • 通过进程等待机制,父进程不仅能回收子进程资源,还能获取其退出状态信息。

三、进程等待方法

1、wait方法

#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int* status);
  • 作用:等待任意一个子进程终止或进入停止状态。

  • 特点:如果没有已终止的子进程,父进程会阻塞(除非设置 WNOHANG 选项)。

  • 返回值:成功时返回被等待进程的PID,失败时返回-1。

  • 参数说明:status:输出型参数,用于获取子进程退出状态。若不关心可设置为NULL。

例如,父进程创建子进程后,可以通过调用wait函数来等待子进程结束,并获取其退出状态信息:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if(id == 0){//childint count = 10;while(count--){printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}exit(0);}//fatherint status = 0;pid_t ret = wait(&status);if(ret > 0){//wait successprintf("wait child success...\n");if(WIFEXITED(status)){//exit normalprintf("exit code:%d\n", WEXITSTATUS(status));}}sleep(3);return 0;
}
  • WIFEXITED(status):检查进程是否正常退出(本质是判断是否收到信号),若正常退出,退出信号为0;不正常退出的话,退出信号则为非0
  • WEXITSTATUS(status):获取进程的退出码 

我们可以使用以下监控脚本对进程进行实时监控:

while :; do ps axj | head -1 && ps axj | grep demo | grep -v grep;echo "######################";sleep 1;done

        这时我们可以看到,当子进程退出后,父进程读取了子进程的退出信息,子进程也就不会变成僵尸进程了:

2、waitpid方法

pid_t waitpid(pid_t pid, int *status, int options);
  • 作用:等待指定的子进程(或一组子进程)终止或状态变化。

  • 特点:可以精确控制等待哪个子进程,支持非阻塞模式。

返回值

  • 当waitpid正常执行时,它会返回被收集子进程的进程ID;
  • 若设置了WNOHANG选项且未发现已退出的子进程,则返回0;
  • 若调用过程中发生错误,则返回-1并设置相应errno值以指示错误类型。

参数说明

通过 pid 参数指定目标进程,options 提供额外控制(如 WNOHANG)。

  1. pid

    • =-1:等待任意子进程(等效于wait)
    • >0:等待其进程ID与pid相等的子进程。
  2. status(输出型参数)

    • WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    • WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  3. options

    • 0:默认阻塞等待
    • WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

        例如,创建子进程后,父进程可使用waitpid函数一直等待子进程(此时将waitpid的第三个参数设置为0),直到子进程退出后读取子进程的退出信息:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if (id == 0){//child          int count = 10;while (count--){printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}exit(0);}//father           int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret >= 0){//wait success                    printf("wait child success...\n");if (WIFEXITED(status)){//exit normal                                 printf("exit code:%d\n", WEXITSTATUS(status));}else{//signal killed                              printf("killed by siganl %d\n", status & 0x7F);}}sleep(3);return 0;
}
  • WIFEXITED(status):检查进程是否正常退出(本质是判断是否收到信号),若正常退出,退出信号为0;不正常退出的话,退出信号则为非0
  • WEXITSTATUS(status):获取进程的退出码 

运行结果: 

        在父进程运行过程中,我们可以尝试使用kill -9命令将子进程杀死,这时父进程也能等待子进程成功:

注意: 被信号杀死而退出的进程,其退出码将没有意义。

3、注意事项

当调用 wait/waitpid 时:

  • 若子进程已退出,函数会立即返回,并释放相关资源,同时获取子进程的退出信息
  • 若子进程仍在正常运行,调用可能会阻塞
  • 若指定的子进程不存在,函数将立即返回错误

4、核心区别

特性waitwaitpid
目标进程任意一个子进程指定 PID 的子进程(或进程组)
阻塞行为默认阻塞可通过 WNOHANG 非阻塞轮询
进程选择灵活性无选择,任意子进程支持多种 PID 选项(见下文)
使用场景简单等待所有子进程精确控制等待的进程或非阻塞检查

5、waitpid 的 PID 参数选项

waitpid 通过 pid 参数指定目标进程:

  • pid > 0:等待指定的子进程(PID = pid)。

  • pid = -1:等待任意子进程(等效于 wait)。

  • pid = 0:等待与父进程同进程组的任意子进程。

  • pid < -1:等待进程组 ID 等于 |pid| 的任意子进程。

6、waitpid 的 options 选项

  • WNOHANG:非阻塞模式,如果没有子进程退出,立即返回 0。

  • WUNTRACED:返回已停止(但未终止)的子进程状态(如被 SIGSTOP 暂停)。

  • WCONTINUED(某些系统):返回已恢复运行的子进程状态(如收到 SIGCONT)。

7、联系

  • wait 是 waitpid 的特例
    wait(&status) 等价于 waitpid(-1, &status, 0)

  • 共享底层机制:两者均通过检查子进程的退出状态(status)来获取信息,使用宏如 WIFEXITEDWEXITSTATUS 解析状态。


四、获取子进程状态

        进程等待使用的两个函数——wait和waitpid,都有一个输出型参数status,该参数由操作系统负责填充。当传入NULL值时,表示父进程不关心子进程的退出状态;否则,操作系统将通过该参数向父进程反馈子进程的退出信息。

        虽然status是一个整型变量,但不能简单地将其视为普通整数。它的不同比特位承载着不同的信息(本说明仅分析status的低16位):

        status的低16位中,高8位存储进程的退出状态(退出码)。若进程因信号终止,低7位记录终止信号,第8位则作为core dump标志位。

Core Dump(了解即可)

  • 在Linux中,core dump标志位(通常指进程的ulimit -c设置或文件系统路径中的/proc/sys/kernel/core_pattern配置)用于控制系统在程序崩溃时是否生成core dump文件,以及如何生成。
  • Core Dump(核心转储)文件 是当 Linux 程序异常崩溃(如段错误、非法指令、被 kill -6 终止等)时,操作系统自动生成的一个内存快照文件。它记录了程序崩溃时的内存状态、寄存器值、堆栈信息等关键数据,主要用于调试和分析程序崩溃原因

通过位操作可以轻松地从status变量中提取进程的退出码和退出信号:

exitCode = (status >> 8) & 0xFF;  // 获取退出码
exitSignal = status & 0x7F;       // 获取退出信号

系统提供了两个便捷的宏来处理这些信息:

  • WIFEXITED(status):检查进程是否正常退出(本质是判断是否收到信号),若正常退出,退出信号为0;不正常退出的话,退出信号则为非0
  • WEXITSTATUS(status):获取进程的退出码

使用示例:

exitNormal = WIFEXITED(status);  // 判断是否正常退出
exitCode = WEXITSTATUS(status);  // 获取实际退出码

需要注意的是,当进程异常退出(被信号终止)时,其退出码将失去实际意义。


五、阻塞与非阻塞等待

1、进程的阻塞等待方式

        上述示例展示了父进程创建并等待单个子进程的情况。实际上,我们还可以同时创建多个子进程,让父进程依次等待它们退出,这就是多进程创建与等待的编程模式。

        比如,在以下代码中,我们一次性创建了10个子进程,并将它们的进程ID存入ids数组。每个子进程的退出码被设置为它在数组中的下标位置。随后,父进程使用waitpid函数逐个等待这10个子进程结束。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t ids[10];for (int i = 0; i < 10; i++){pid_t id = fork();if (id == 0){//childprintf("child process created successfully...PID:%d\n", getpid());sleep(3);exit(i); //将子进程的退出码设置为该子进程PID在数组ids中的下标}//fatherids[i] = id;}for (int i = 0; i < 10; i++){int status = 0;pid_t ret = waitpid(ids[i], &status, 0);if (ret >= 0){//wait child successprintf("wiat child success..PID:%d\n", ids[i]);if (WIFEXITED(status)){//exit normalprintf("exit code:%d\n", WEXITSTATUS(status));}else{//signal killedprintf("killed by signal %d\n", status & 0x7F);}}}return 0;
}

运行结果:

        运行代码,这时我们便可以看到父进程同时创建多个子进程,当子进程退出后,父进程再依次读取这些子进程的退出信息。

2、进程非阻塞等待实现

        在之前的例子中,当子进程尚未退出时,父进程会持续等待,在此期间父进程无法执行其他任务,这种等待方式称为阻塞等待。

        实际上,我们可以采用非阻塞等待的方式:在子进程未退出时,父进程可以继续处理其他事务,待子进程退出后再获取其状态信息。

        实现方法很简单:只需在调用waitpid函数时,将第三个参数options设为WNOHANG。这样设置后,若子进程仍在运行,waitpid会立即返回0;若子进程已正常结束,则返回该子进程的PID。

        例如,父进程可以定期调用waitpid进行检查。若子进程尚未退出,父进程可先处理其他任务,稍后再尝试获取子进程的退出状态信息。

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>typedef void (*handler_t)();  // 函数指针类型定义
std::vector<handler_t> handlers;  // 任务处理函数容器void task_one() {printf("执行临时任务1\n");
}void task_two() {printf("执行临时任务2\n");
}void load_tasks() {handlers.push_back(task_one);handlers.push_back(task_two);
}void execute_tasks() {if (handlers.empty()) load_tasks();for (auto task : handlers) {task();}
}int main() {pid_t pid = fork();if (pid < 0) {printf("%s: 进程创建失败\n", __FUNCTION__);return 1;} else if (pid == 0) {  // 子进程printf("子进程运行中,PID: %d\n", getpid());sleep(5);exit(1);} else {  // 父进程int status = 0;pid_t ret = 0;do {ret = waitpid(-1, &status, WNOHANG);  // 非阻塞等待if (ret == 0) {printf("子进程仍在运行\n");}execute_tasks();} while (ret == 0);if (WIFEXITED(status) && ret == pid) {printf("成功等待子进程5秒,返回码: %d\n", WEXITSTATUS(status));} else {printf("等待子进程失败\n");return 1;}}return 0;
}

该代码要使用到g++编译器,首先我们要安装g++:

sudo yum install gcc-c++

auto 是 C++11 引入的特性,如果编译器较旧,需添加 -std=c++11

g++ demo1.cpp -o demo1 -std=c++11

运行结果:

        父进程定时轮询检查子进程的退出状态。若子进程仍在运行,父进程会先处理其他任务,稍后再继续轮询检查,直至子进程退出后获取其终止信息。

http://www.dtcms.com/a/284962.html

相关文章:

  • kimi故事提示词 + deepseekR1 文生图提示
  • milvus向量数据库连接测试 和 集合维度不同搜索不到内容
  • windows利用wsl安装qemu
  • 利用deepspeed在Trainer下面微调大模型Qwen2.5-3B
  • SpringBoot01-springBoot的特点
  • 登录功能实现深度解析:从会话管理到安全校验全流程指南
  • 【算法训练营Day13】二叉树part3
  • 【中等】题解力扣21:合并两个有序链表
  • 教你使用bge-m3生成稀疏向量和稠密向量
  • 大语言模型系列(1): 3分钟上手,在骁龙AI PC上部署DeepSeek!
  • 【Lua】题目小练2
  • LIN协议核心详解
  • c++之 KMP 讲解
  • Cocos游戏中UI跟随模型移动,例如人物头上的血条、昵称条等
  • C++中,不能声明为虚函数的函数类型
  • C++进阶-AVL树(平衡二叉查找树)(难度较高)
  • 2025 XYD Summer Camp 7.17 模考
  • Vue.js 响应式原理深度解析:从 Vue 2 的“缺陷”到 Vue 3 的“涅槃重生”
  • OpenVela之网络驱动适配指南
  • JxBrowser 7.43.5 版本发布啦!
  • ​​Sublime Text 2.0.2.2221 安装教程 - 详细步骤指南(附下载与配置)​
  • 深入解析:Chunked Prefill 与 FlashAttention/FlashInfer 如何协同工作
  • WSL2 离线安装流程
  • 如何让订货系统支持多角色?
  • 药品通用名、商品名、规格剂型查询API接口-中国药品批文数据库
  • 深度学习之优化方法
  • 页面登录阻止浏览器提醒是否保存密码
  • 算法讲解-移动零
  • 面试Redis篇-深入理解Redis缓存击穿
  • HTML 常用语义标签与常见搭配详解