如何正确终止fork()创建的进程且无需阻塞等待其结果
根据任务要求,程序需调用fork()创建自身副本处理任务,且子进程运行期间父进程不得被阻塞。
测试过程中出现两个异常现象:
pgrep查询结果显示子进程直到父程序执行完毕才会终止- 每次调用
fork()后,pgrep统计的进程数持续增长,已运行结束的子进程并未被正常回收
测试用基础程序总运行时长约35秒,运行期间共生成3次子进程,预期每个子进程运行5秒后自动退出,代码如下:
#include <stdio.h> #include <unistd.h> int main(int argc, char* argv[]) { for (int i = 1; i < 36; i++) { if (i % 10 == 0) { pid_t forkrank = fork(); if (forkrank == 0) { printf("forked process starts\n"); sleep(5); return 1; } } sleep(1); } return 0; }
该程序运行期间pgrep显示子进程直到父进程结束才会被清理,运行结果截图:
如果添加阻塞代码等待子进程执行结果,子进程就可以被正常终止回收,对应代码如下:
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main(int argc, char* argv[]) { for (int i = 1; i < 36; i++) { if (i % 10 == 0) { pid_t forkrank = fork(); if (forkrank == 0) { printf("forked process starts\n"); sleep(5); return 1; } int wstatus; waitpid(forkrank, &wstatus, 0); int retValue = WEXITSTATUS(wstatus); } sleep(1); } return 0; }
对应pgrep正常查询结果截图:
核心疑问:
- 该现象的产生原因是什么?
- 如何在不阻塞主进程的前提下正常终止并回收
fork()创建的子进程?
这是Unix/Linux进程模型里的僵尸进程(zombie process)机制,不是子进程没正常退出:
- 子进程执行完退出逻辑后,内核不会立刻完全释放它的进程表项,会保留PID、退出码、资源使用统计等最小信息,等待父进程调用
wait()/waitpid()取走这些信息。这个状态下的子进程就是僵尸进程,仍然会被pgrep这类进程查询工具统计到。 - 如果父进程始终不调用wait类函数回收,子进程就会一直保持僵尸状态,直到父进程终止。父进程退出后,所有残留的僵尸子进程会被PID为1的init进程接管,由init统一完成回收,这就是观察到“子进程等父进程结束才消失”的原因。
- 加了阻塞式
waitpid之后能正常回收,是因为这个调用会暂停父进程执行,直到对应子进程退出、内核释放子进程资源后才返回,自然不会留僵尸,但也阻塞了主流程,不符合需求。
两种成熟方案,可按需选择:
方案1:非阻塞轮询waitpid
给waitpid传入WNOHANG参数,调用时如果没有已经退出的子进程,函数会立刻返回0,不会阻塞主流程。只需要在主循环的每次迭代里插入一小段非阻塞回收逻辑,就能在不影响主任务执行的前提下清理退出的子进程。
核心实现参考:
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main(int argc, char* argv[]) { for (int i = 1; i < 36; i++) { if (i % 10 == 0) { pid_t forkrank = fork(); if (forkrank == 0) { printf("forked process starts\n"); sleep(5); return 1; } } // 非阻塞回收所有已退出的子进程 int wstatus; pid_t done_pid; while ((done_pid = waitpid(-1, &wstatus, WNOHANG)) > 0) { int exit_code = WEXITSTATUS(wstatus); // 这里可以按需处理子进程退出逻辑,比如打日志 printf("child %d exited, code=%d\n", done_pid, exit_code); } sleep(1); } return 0; }
这里waitpid第一个参数传-1表示匹配任意子进程,套while循环是为了一次轮询就把所有已经退出的子进程全部回收,避免多个子进程同时退出时漏处理。
方案2:忽略SIGCHLD信号让内核自动回收
如果不需要获取子进程的退出状态,最简单的做法是在程序初始化时直接设置忽略SIGCHLD信号,内核会在子进程退出后自动回收其资源,完全不会产生僵尸进程,不需要写任何wait相关逻辑。
只需要在main函数开头加两行代码:
#include <signal.h> // 记得补充头文件 signal(SIGCHLD, SIG_IGN);
说明:这个行为是POSIX标准明确规定的,所有主流Linux发行版、macOS等BSD系统均支持。如果业务需要感知子进程退出、拿到退出码,就不能用这个方案,要么用上面的WNOHANG轮询,要么注册SIGCHLD信号处理函数,在信号回调里调用waitpid回收。
内容的提问来源于stack exchange,提问作者Ivan

