0

可能重复:
fork() 和输出

通过运行:

#include<stdio.h>
int main()
{
    fork();
    printf("b");
    if (fork() == 0) {
        write(1, "a", 1);
    }else{
        write(1, "c", 1);
    }
    return 0;
}

我得到了cbcabbab,有人可以向我解释一下输出吗?如果可能的话,是否有一个工具可以逐步查看运行过程?

4

3 回答 3

2

简短的回答:不要混合缓冲和非缓冲代码。

长答案:让我们使用以下命令测试源代码的变体:

$ rm dump
$ for X in 0 1 2 3 4 5 6 7 8 9; do for Y in 0 1 2 3 4 5 6 7 8 9; do for Z in 0 1 2 3 4 5 6 7 8 9; do echo `./program` >> dump; done; done; done
$ sort -u dump

这会执行program一千次,并列出它返回的所有唯一输出。

缓冲版本:替换writefwrite(或printf

#include <unistd.h>
#include <stdio.h>

int main()
{
    fork();
    printf("b");
    if (fork() == 0) {
        fwrite("a", 1, 1, stdout);
    }else{
        fwrite("c", 1, 1, stdout);
    }
    return 0;
}

这给出了一个非常规则的输出模式。事实上,只有六个输出是可能的:

bababcbc
babcbabc
babcbcba
bcbababc
bcbabcba
bcbcbaba

到底是怎么回事?

  1. 第一次分叉后,有两个进程,W和Y。
  2. "b"两个进程都向stdout流写入一封信。默认情况下,流是缓冲的,因此 .
  3. 在第二次分叉之后,有四个进程:W 和 X,Y 和 Z。W 和 X 的stdout流具有相同的状态,因此也包含相同的缓冲区"b"。Y 和 Z 也是如此。
  4. stdout所有四个进程都向流中写入另一个字母。
  5. 返回后main,C 运行时接管。每个进程都会刷新它们的缓冲区,包括stdout.

无缓冲版本:替换printfwrite

#include <unistd.h>

int main()
{
    fork();
    write(1, "b", 1);
    if (fork() == 0) {
        write(1, "a", 1);
    }else{
        write(1, "c", 1);
    }
    return 0;
}

可能的输出现在更加多样化了,但考虑到并发性,它仍然很容易理解:

bbacca
bbcaac
bbcaca
bbccaa
bcabca
bcbaca

这可能是您预期的输出。

混合版(你的)

您的代码比前两个变体提供了更多的结果:

cabbacbb
cabbcabb
cabbcbab
cabcabbb
cabcbabb
cabcbbab
... etc ...

这是因为write调用会立即产生输出,但缓冲的"b"只会在每个进程终止时打印,当然是在调用之后write。就像在完全缓冲的版本中一样,每个进程都会"b"stdout缓冲区中拥有它,所以你最终会看到其中的四个。

于 2013-02-01T15:00:18.393 回答
1

再次尝试运行它,您可能会得到不同的输出。

至于逐步查看过程的工具,我认为strace -f可能会有所帮助:

$ strace -f ./weirdfork
execve("./weirdfork", ["./weirdfork"], [/* 35 vars */]) = 0
... uninteresting boiler plate removed ...
clone(Process 8581 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8581
[pid  8580] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
[pid  8581] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
[pid  8580] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...>
[pid  8581] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...>
[pid  8580] <... mmap resumed> )        = 0x7fe1c7d22000
[pid  8581] <... mmap resumed> )        = 0x7fe1c7d22000
[pid  8581] clone( <unfinished ...>
[pid  8580] clone(Process 8582 attached
 <unfinished ...>
[pid  8581] <... clone resumed> child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8582
Process 8583 attached
[pid  8580] <... clone resumed> child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fe1c7d0b9d0) = 8583
[pid  8580] write(1, "c", 1 <unfinished ...>
[pid  8581] write(1, "c", 1cc)            = 1
[pid  8580] <... write resumed> )       = 1
[pid  8581] write(1, "b", 1b <unfinished ...>
[pid  8580] write(1, "b", 1 <unfinished ...>
[pid  8581] <... write resumed> )       = 1
b[pid  8581] exit_group(0)               = ?
Process 8581 detached
[pid  8580] <... write resumed> )       = 1
[pid  8580] exit_group(0)               = ?
[pid  8583] write(1, "a", 1 <unfinished ...>
[pid  8582] write(1, "a", 1a)            = 1
a[pid  8582] write(1, "b", 1 <unfinished ...>
[pid  8583] <... write resumed> )       = 1
[pid  8583] write(1, "b", 1b)            = 1
[pid  8583] exit_group(0)               = ?
Process 8583 detached
b<... write resumed> )                   = 1
exit_group(0)                           = ?
Process 8582 detached
于 2013-02-01T13:40:51.507 回答
0

除非您专门添加代码来同步您的分叉进程,否则它们将完全独立运行,因此输出顺序是完全“随机的”。进程调度将决定谁下一个运行,这又取决于系统中有多少个处理器内核,还有什么正在运行,以及当前运行的每个进程已经运行了多长时间。

如链接中所述,您还可以从 的内部缓冲区获取输出printf,因为输出尚未写入代表的实际文件stdout- 您可以通过添加fflush(stdout);after来“修复”该问题printf

于 2013-02-01T14:41:39.263 回答