关于fork和printf / write

时间:2013-02-01 13:34:35

标签: c buffer fork

  

可能重复:
  fork() and output

运行:

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

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

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

简短回答:不要混用缓冲和无缓冲的代码。

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

$ 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一千次,并列出它返回的所有唯一输出。

缓冲版本:将write替换为fwrite(或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
  6. 的缓冲区

    无缓冲版本:将printf替换为write

    #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",这是 {{1}之后的当然,打电话。就像在完全缓冲版本中一样,每个进程在write缓冲区中都会有"b",因此您最终会看到其中的四个。

答案 2 :(得分:0)

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

如链接中所述,您还可以从printf的内部缓冲区获得输出,因为输出尚未写入代表stdout的实际文件中 - 您可以“修复”该内容在fflush(stdout);之后添加printf