在 Linux 系统编程中,父进程需要通过某种机制等待子进程的结束,以便清理资源、防止僵尸进程的产生。wait 和 waitpid 是我们最常用的两种方式。虽然它们都用于等待子进程的状态变化,但在功能和使用灵活性方面,waitpid 更加强大。本篇文章将深入讲解它们的使用方式和异同点,并结合代码实例帮助理解。
一、wait
1、函数原型
pid_t wait(int *status);
2、参数说明
-
int *status
:这是一个输出参数,用于接收子进程的退出状态信息。- 如果你不关心退出状态,可以传
NULL
。
wait(NULL);
wait(NULL)
表示: 父进程会阻塞,直到任意一个子进程退出; 但不会获取子进程的退出状态,因为你没有传入status
参数的地址。适用于父进程只想等子进程结束,不关心其结果。代码举例:
#include <iostream> #include <sys/wait.h> #include <unistd.h> int main() { pid_t pid = fork(); if (pid == 0) { std::cout << "子进程:执行任务并退出...\n"; return 123; } // 父进程不关心退出状态,只等子进程结束 wait(NULL); std::cout << "父进程:子进程已退出,但我不关心退出码\n"; return 0; }
- 如果你不关心退出状态,可以传
-
如果传入的是一个指针,在子进程退出时,它会被填入状态码(不是直接的退出值,需要解析)。
3、返回值
成功:返回已终止子进程的 PID
失败:返回 -1,并设置 errno
4、如何解析status
在这里举两个例子
示例1:正常退出(WIFEXITED + WEXITSTATUS
)
#include <iostream>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
std::cout << "Child: 正常退出\n";
return 5; // 子进程返回5
}
int status;
wait(&status); // 等待任意子进程退出
if (WIFEXITED(status)) {
std::cout << "父进程:子进程正常退出,退出码 = "
<< WEXITSTATUS(status) << "\n"; // 应输出5
}
return 0;
}
示例2:被信号杀死(WIFSIGNALED + WTERMSIG
)
#include <iostream>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
std::cout << "Child: 无限循环中,等待被杀...\n";
while (true) {} // 模拟卡死
}
sleep(1); // 等待子进程运行
kill(pid, SIGKILL); // 杀死子进程(信号9)
int status;
wait(&status);
if (WIFSIGNALED(status)) {
std::cout << "父进程:子进程被信号杀死,信号编号 = "
<< WTERMSIG(status) << "\n"; // 应输出9
}
return 0;
}
二、waitpid
waitpid
是 Linux/Unix 系统编程中用于等待子进程状态变化的系统调用,它比 wait
更灵活。
1、waitpid函数原型
pid_t waitpid(pid_t pid, int *status, int options);
2、 参数说明:
pid
:-1
:等待任意一个子进程(相当于wait
)。> 0
:等待进程号为pid
的子进程。0
:等待和当前进程同一进程组的任意子进程。< -1
:等待进程组ID为|pid|
的任何子进程。
status
:用于接收子进程的状态信息(可以通过宏解析)。options
:0
:阻塞等待。WNOHANG
:非阻塞等待,如果没有子进程退出,立即返回。WUNTRACED
:当子进程停止(如收到SIGSTOP
)也返回。
3、返回值
成功:返回子进程的 PID。
如果使用了 WNOHANG 且没有子进程退出:返回 0。
失败:返回 -1,并设置 errno。
4、示例代码:
#include <iostream>
#include <sys/wait.h>
#include <unistd.h>
pid_t pid = fork();
if (pid == 0) {
// 子进程代码
exit(0);
} else {
int status;
pid_t ret = waitpid(pid, &status, 0); // 阻塞等待特定子进程结束
if (WIFEXITED(status)) {
printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));
}
}
读到这里,我们可以看出wait等价于:waitpid(-1, status, 0)
,也就是说它只能阻塞等待任意一个子进程。
三、总结
特性 | wait | waitpid |
---|---|---|
是否阻塞 | 是 | 可选(通过 options 控制) |
是否支持指定子进程 | 否(任意子进程) | 是(可指定特定 PID) |
能否等待停止的子进程 | 否 | 是(带 WUNTRACED ) |
返回值 | 子进程 PID 或 -1(无子进程) | 子进程 PID 或 0(非阻塞无变化) |
在日常开发中,如果只是简单地等待任意一个子进程结束,wait 是足够的;但当你需要对特定子进程进行控制、或者实现非阻塞进程管理时,waitpid 就是更合适的选择。