考虑在 Solaris 11.3 下运行的以下代码:
int main(void) {
pid_t pid = fork();
if (pid > 0) {
printf("[%ld]: Writing from parent process\n", getpid());
}
if (pid == 0) {
execl("/usr/bin/cat", "/usr/bin/cat", "file.c", (char *) 0);
perror("exec failed");
exit(1);
}
}
每当我运行它时,“从父级写入”行总是最后输出。如果我的学校任务不是使用 wait(2) 以便仅在子进程完成后打印该行,我不会对此结果感到惊讶。为什么会发生这种情况以及如何确保在子进程执行 cat 之前打印此行(或者顺序至少未定义),以便我可以安全地使用 wait(2) 或 waitpid(2) 来解决这个问题?
答案1
正如 @AndrewHenle 评论的那样,依赖系统以特定顺序安排进程是不安全且不合理的。即使调度看起来是一致的(如您的情况),也没有什么可以阻止操作系统的实现者改变调度程序的行为。
如果进程/线程之间的操作顺序相关,则需要某种形式的通信。
对于您的场景,简单的阻塞读取就可以完成这项工作。在叉子之前创建一个管道。然后仅在父级打印其消息后才从父级写入管道。同时,子进程尝试从管道中读取数据将阻止其执行,直到父进程写入为止。
忽略每个进程中的错误处理和未使用的管道文件描述符(通常在分叉后显式关闭):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void) {
char buf[1];
int pipe_fds[2];
pipe(pipe_fds);
pid_t pid = fork();
if (pid > 0) {
printf("[%ld]: Writing from parent process\n", getpid());
write(pipe_fds[1], "_", 1);
wait(NULL);
}
if (pid == 0) {
read (pipe_fds[0], buf, 1);
execl("/usr/bin/cat", "/usr/bin/cat", "file.c", (char *) 0);
perror("exec failed");
exit(1);
}
您可能应该在父级中使用 wait,这样如果从终端运行它,子级的输出和 shell 提示符就不会交错。
答案2
从内核的角度考虑这一点:它只是通过设置子进程来破坏各种缓存,接下来运行该子进程可能会重用一些新的缓存内容。同时,父进程可能会被调度到另一个 CPU 上。
答案3
虽然原则上两个进程可以并行运行,但父进程已经在 CPU 上处于活动状态,而子进程必须先创建exec
新进程才能创建其输出。父进程很可能在子进程替换其进程之前完成。