父进程等待子进程结束可以通过两种方式实现:阻塞等待和非阻塞等待。这两种方式各有优缺点,适用于不同的场景。
简单来说:
阻塞等待:先等你,我再继续
非阻塞等待:不等你,我继续做自己的事,期间不断看看你行没行
1. 函数 waitpid
pid_ t waitpid(pid_t pid, int *status, int options);
参数说明
pid_ t
:返回值
-
>0
: 子进程ID -
=0
: 当选项 options 设置成 WNOHANG, 表示非阻塞等待, 若没有子进程退出,则返回 0 -
<0
: 该函数调用出错
pid
:指定要等待的子进程的 PID(进程标识符)。
- 如果
pid = -1
,则等待任意一个子进程。 - 如果
pid = 0
,则等待当前进程所在进程组中的任意一个子进程。 - 如果
pid > 0
,为具体的子进程 PID,则等待指定的子进程。 - 如果
pid > 0
,但不是当前进程的子进程的 PID,则waitpid()
会返回-1
并设置errno
为ECHILD
。
status
:一个指向 int
类型的指针,用于接收子进程的状态信息。该参数是一个输出型参数,由操作系统填充
如果传递 NULL / nullptr
,表示不关心子进程的退出状态信息。
- 如果子进程正常退出,
status
包含退出码。 - 如果子进程异常退出:因信号而终止,
status
包含信号编号。 - 如果子进程异常退出:被停止或继续执行,
status
包含相应的信号编号。
options
:指定等待的选项,可以是以下标志之一或组合:
-
0
:默认等待,阻塞直到一个子进程结束。 -
WNOHANG
:非阻塞等待,如果没有任何子进程结束,则立即返回 0,不阻塞当前进程.若正常结束,则返回该子进程的 ID。 -
WUNTRACED
:报告被跟踪的子进程(即使它们尚未停止)。 -
WCONTINUED
:报告被继续执行的子进程(即被SIGCONT
信号继续执行)
关于参数 status
参数 status 不能简单的当作整型来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
上面演示图表示两种进程终止,两种情况下参数 status 的两种数值表示
(1)当进程正常终止时:15~8 号为 进程退出码
(2)当进程异常终止(信号所杀):15~8 号 不用,7 号 core dump,6 ~ 0 为终止信号
进程退出码 和 进程异常终止的终止信号 可以通过对参数 status 进行 位运算
获取
我们可以通过系统写好的宏来获取:底层是通过对 waitpid 函数参数 status 进行位运算,取对应部分的数值
详细如何使用相关系统宏,可以看这篇博客:如何通过系统宏定义,获取进程的退出码或退出信号
2. 阻塞等待(Blocking Wait)
阻塞等待意味着父进程会一直等待,直到子进程结束或出现错误为止。
通常使用 wait()
或带有默认选项的 waitpid()
函数实现(即 参数 options = 0
):waitpid(pid, &status, 0);
3. 非阻塞等待(Non-blocking Wait)
非阻塞等待允许父进程在没有子进程结束的情况下继续执行其他任务。
通常使用带有 WNOHANG
选项的 waitpid()
函数实现(即 参数 options = WNOHANG
):waitpid(pid, &status, WNOHANG);
4. 阻塞等待 vs 非阻塞等待
优点
-
阻塞等待:
- 实现简单。
- 确保父进程在子进程结束后继续执行。
-
非阻塞等待:
- 父进程可以继续执行其他任务,提高程序效率。
- 适用于需要同时处理多个子进程或其他任务的情况。
缺点
-
阻塞等待:
- 父进程在此期间不能做其他事情,降低了程序的整体效率。
- 如果子进程长时间不结束,父进程会一直处于等待状态。
-
非阻塞等待:
- 实现相对复杂,需要循环检查子进程状态。
- 如果频繁检查子进程状态,可能会增加不必要的CPU负载。
5. 应用场景选择
-
阻塞等待:
- 适用于只需要等待一个子进程结束的简单场景。
- 当父进程必须等待子进程结束后才能继续执行时。
-
非阻塞等待:
- 适用于需要同时处理多个子进程或执行其他任务的复杂场景。
- 当父进程在等待子进程的同时还需要处理其他事情时。
通过选择合适的等待方式,可以根据具体的应用需求和场景优化程序的设计和实现。