3

I have this tiny program in C

main(int argc, char **argv)
{
    forkit(4);
}
void forkit(int n)
{
if(n > 0)
    {
        fork();
        printf("%d \n", n);
        forkit(n-1);
    }
}

which prints

4 4 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

30 numbers, all in new lines. However, if I remove the \n in the printf statement it prints this:

4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1

Without new line it gives 64 numbers.

How is such a small change giving such different results?

Mateusz Bartkowski
  • 709
  • 1
  • 12
  • 29
  • 1
    When I run it: fork > out.txt && wc -l out.txt -- I get 64 (leaving the newline in) – Thomas Dignan Mar 03 '17 at 00:25
  • The number `n` is not in the same address space I think. Each new process you create (which `fork` does) will be a new instance of your process. – Chol Nhial Mar 03 '17 at 00:26
  • @TomDignan same as well I get 64 with or without the newline. – Chol Nhial Mar 03 '17 at 00:41
  • A couple of observations here: (1)I don't believe printf is threadsafe. You need to put a mutex around this call. Observation (2), every time printf encounters a newline, it will force a flush. This will change your timing characteristics. Starting to write to disk is a sure way to switch threads. edit: DigitalRoss's explanation is more complete. Please refer to that. – ChiralMichael Mar 03 '17 at 00:44
  • seems like it depends on the buffering mode, if I flush the buffer with `fflush` I get 30 even without the new line (which is the number of processes) – Mateusz Bartkowski Mar 03 '17 at 00:45
  • @ChiralMichael These are separate processes, not separate threads. Mutexes won't help, and won't work since they're per-process. – John Kugelman Mar 03 '17 at 00:57

1 Answers1

6

Very good question, there is something subtle going on.

What's happening is that printf's output is line buffered and flushed at newlines.

All by itself, that doesn't affect the output of most programs, just the speed.

But when you fork, buffered I/O that hasn't yet been output is now present in both children. With the newline in that output has already happened so it is no longer pending in the child.

That's the explanation. If it isn't clear, another way to phrase it is: without the newline, the pending output is multiplied by the number of future children in the process tree. With the newline, it's just output that happened in the past and the program works like you expect it to.

Note that the output eventually gets flushed even without the newline, but it happens when the program calls exit(3). By then, it has already forked and passed on it's pending (i.e., buffered) output.

By the way, if you redirect the output to a file, it will be block buffered and you will probably see similar results (except for the newlines) in both cases.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
  • Thank you for your answer. Now I am just unsure why it gives 64 numbers (`4 3 2 1` for each of the 16 "leaf" processes that have n=1) and not print others, like `4 3 2` for the 8 processes with n=2? – Mateusz Bartkowski Mar 03 '17 at 00:53
  • Note: This is why it is a good idea to, at the very least, flush `stdout` (`stderr` is usually unbuffered, so it's okay anyway), and ideally flush (or close) all other open file descriptors prior to forking. – ShadowRanger Mar 03 '17 at 00:56
  • I just wonder, am i right that this Problem can be os and even platform dependent? – maxbit89 Mar 03 '17 at 01:06
  • @MateuszBartkowski fork splits each process in 2; half of the leaf processes are the 8 processes from the prior level of the tree. – zstewart Mar 03 '17 at 01:06