进程等待的必要性
子进程等待的两个方法 wait 与 waitpid
1:wait方法:
wait
函数用于等待一个已启动的子进程结束。这个函数通常与 fork
函数一起使用,fork
用于创建子进程。wait
函数会挂起(父进程阻塞等待)调用它的父进程,直到有一个子进程结束或到达指定的时间限制。
头文件:
#include <sys/types.h>
#include <sys/wait.h>
函数原型:
pid_t wait(int *status);
status
:输出型参数,一个指向整数的指针,如果提供一个变量,子进程的结束状态将被存储在所指向的整数变量中
子进程退出结束状态的三种情况:1:代码运行完毕,结果正确
2:代码运行完毕,结果不正确
3:异常终止
退出码 (status>>8)&0xFF 终止信息 status&0x7F
返回值:
- 成功时,
wait
返回结束的子进程的进程ID。 - 如果有错误发生,返回
-1
,并设置errno
以指示错误类型。
例子:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
pid_t pid;
int status;
pid = fork(); // 创建子进程
if (pid == -1) // 返回值是1时,说明fork() 调用失败
{
perror("fork");
exit(EXIT_FAILURE);
}
else if (pid == 0) // 子进程
{
printf("Child process, PID: %d, PPID: %d\n", getpid(), getppid());
exit(EXIT_SUCCESS);
}
else // 父进程
{
printf("Parent process, PID: %d, PPID: %d\n", getpid(), getppid());
pid = wait(&status); // 挂起直到子进程结束
if (pid == -1) // 返回值是1时,说明wait() 调用失败
{
perror("wait");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status)) // 最后查看检查子进程的结束状态
{
printf("Child process exited with status %d\n", WEXITSTATUS(status));
}
}
return 0;
}
这个例子中,父进程使用 fork
创建了一个子进程。
子进程打印出它的进程ID和父进程ID,然后退出。
父进程则调用 wait
函数并传入一个指向整数的指针,这个整数将存储子进程的结束状态。
因为有wait方法存在,一旦子进程结束,父进程将继续执行,并检查子进程的结束状态。
2:waitpid方法:
与 wait
函数不同,waitpid
允许父进程指定要等待的子进程的进程标识符(PID),这提供了更细粒度的控制。
waitpid
对于处理多个子进程的情况特别有用,因为它允许父进程等待特定的子进程或一组子进程。
头文件与wait文件相同:
#include <sys/types.h>
#include <sys/wait.h>
函数原型:
pid_t waitpid(pid_t pid, int *status, int options);
pid_t pid
:指定要等待的子进程的 PID。- 如果
pid
> 0,waitpid
等待特定的pid子进程。 - 如果
pid
== -1,waitpid
等待任何子进程,类似于wait
函数。 - 如果
pid
< -1,waitpid
等待进程组 ID(PGID)等于pid
绝对值的任何子进程。 -
---------------------------------------------------------------------------------------------------------------------------
int *status
:一个指向整数的指针,如果提供一个变量,结束状态将被存储在所指向的整数变量中-
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
-
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
- ---------------------------------------------------------------------------------------------------------------------------
int options
:指定waitpid
调用的行为的选项,-
WNOHANG
(非阻塞轮询,使等待非阻塞)若 pid 指定的子进程没有结束,则 waitpid() 函数返回 0 ,不予以等待。若正常结束,则返回该子进程的ID 。 -
WUNTRACED
(除了已结束的子进程,还报告已停止的子进程)。 -
0 表示默认选项
-
---------------------------------------------------------------------------------------------------------------------------
返回值与wait一样:
- 成功时,
wait
返回结束的子进程的进程ID。 - 如果有错误发生,返回
-1
,并设置errno
以指示错误类型。
例子:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
pid_t pid, wpid;
int status;
// 创建子进程
pid = fork();
if (pid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) // 子进程
{
printf("Child process, PID: %d, PPID: %d\n", getpid(), getppid());
sleep(5);
printf("Child process terminating\n");
exit(EXIT_SUCCESS);
}
// waitpid方法,父进程等待特定的子进程结束
wpid = waitpid(pid, &status, 0);
if (wpid == -1)
{
perror("waitpid");
exit(EXIT_FAILURE);
}
printf("Child process exited with status %d\n", WEXITSTATUS(status));
return 0;
}
在这个示例中,父进程创建了一个子进程,
一旦sleep(5);5s过后,子进程结束,waitpid
返回子进程的 PID,父进程可以检查子进程的退出状态。
阻塞轮询例子
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int status;
pid = fork(); // 创建子进程
if (pid < 0) // fork 出错
{
perror("fork failed");
exit(EXIT_FAILURE);
}
else if (pid == 0) // 子进程
{
printf("Child process, PID: %d\n", getpid());
sleep(5);
exit(0);
}
else // 父进程
{
while (1)
{
pid_t ret = waitpid(pid, &status, WNOHANG); // 非阻塞等待
if (ret == 0) // 子进程未结束,继续轮询
{
printf("Child process is still running.\n");
sleep(1); // 等待1秒后再次检查
}
else if (ret == pid) // 子进程已结束
{
if (WIFEXITED(status))
{
printf("Child process exited with status %d\n", WEXITSTATUS(status)); //提取子进程退出码
}
else if (WIFSIGNALED(status))
{
printf("Child process terminated by signal %d\n", WTERMSIG(status)); //终止子进程信号
}
break;
}
else // waitpid 出错
{
perror("waitpid failed");
exit(EXIT_FAILURE);
}
}
}
return 0;
}
多个进程等待
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
//0--5s 子进程运行,父进程运行
//5--15s 子进程回收,父进程运行
int main()
{
pid_t child_pid;
int status;
int i;
for (i = 0; i < 5; ++i) // 创建5个子进程
{
child_pid = fork();
if (child_pid == -1) // fork() 调用失败
{
perror("fork");
exit(EXIT_FAILURE);
}
if (child_pid == 0) // 这是子进程,每个子进程运行5s
{
int n = 5;
while (n--)
{
printf("Child process, PID: %d, PPID: %d\n", getpid(), getppid());
sleep(1);
}
exit(EXIT_SUCCESS);
}
}
for (i = 0; i < 5; ++i)
{
if (wait(&status) == -1) //循环5次wait来等待创建的5个子进程
{
perror("wait");
exit(EXIT_FAILURE);
}
// 检查子进程的结束状态
if (WIFEXITED(status))
{
printf("Child process exited with status %d\n", WEXITSTATUS(status));
}
}
sleep(10); //父进程等待10s
return 0;
}
我们首先通过一个 for
循环调用 fork()
五次,创建五个子进程。
每个子进程都会打印出自己的 PID 和 PPID,然后调用 sleep(1)
以模拟执行任务,接着通过 exit()
函数正常退出。
父进程在创建了所有子进程之后,会进入另一个 for
循环,使用 wait()
函数等待每个子进程结束。
wait()
会挂起父进程,直到有一个子进程结束。一旦有子进程结束,wait()
返回,父进程会检查子进程的结束状态,如果子进程是正常结束的,会打印出子进程的退出状态码。