【问题标题】:About fork and printf/write [duplicate]关于 fork 和 printf/write [重复]
【发布时间】:2013-01-16 20:01:35
【问题描述】:

可能重复:
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,有人可以向我解释一下输出吗?如果可能的话,有没有一个工具可以一步步查看运行过程?

【问题讨论】:

标签: c buffer fork


【解决方案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. 两个进程都向stdout 流写入一封信"b"。流是默认缓冲的,所以。
  3. 在第二次分叉之后,有四个进程:W 和 X,Y 和 Z。W 和 X 的 stdout 流具有相同的状态,因此相同的缓冲区也只包含 "b"。 Y 和 Z 也是如此。
  4. 所有四个进程都向stdout 流写入另一封信。
  5. main 返回后,C 运行时接管。每个进程都会刷新它们的缓冲区,包括stdout 的缓冲区。

无缓冲版本:将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" 只会在每个进程终止时打印,当然这是在write 调用之后。就像在完全缓冲的版本中一样,每个进程都会在 stdout 缓冲区中拥有 "b",因此您最终会看到其中的四个。

【讨论】:

    【解决方案2】:

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

    至于一步一步查看过程的工具,我认为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
    

    【讨论】:

      【解决方案3】:

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

      正如链接中所解释的,您还可以从printf 的内部缓冲区获得输出,因为输出尚未写入代表stdout 的实际文件中 - 您可以通过添加 @ 来“修复”它987654323@ 在printf 之后。

      【讨论】:

      • 我认为隐含的混淆在于b的重复,而不是字母顺序。
      • 啊,链接到上一篇文章的评论解释得很好,是的......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-08
      • 2013-07-01
      • 2015-01-03
      • 2014-01-31
      • 1970-01-01
      • 2018-06-16
      • 2013-12-13
      相关资源
      最近更新 更多