2

I am running the following code which I wrote in c on my Mac.

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

int main(int argc, const char * argv[]) {

    int i = 0;
    printf("Starting pid:%d\n", getpid());
    while(i++<3){
        int ret = fork();
        printf("hello(%d)%d, %d ", i, getpid(), ret);
        if(!ret){
            printf("\n");
            return 0;}
    }
}

The goal I had in mind was for there to be 6 prints in total - 3 from the original parent (the pid printed in "Starting pid"), and 1 more from each of the 3 spawned children. Instead what I received was:

Starting pid:7998
hello(1)8009, 0 
hello(1)7998, 8009 hello(2)8010, 0 
hello(1)7998, 8009 hello(2)7998, 8010 hello(3)7998, 8011 hello(1)7998, 8009 hello(2)7998, 8010 hello(3)8011, 0 

Note that the original parent (7998) prints 6 times (three times when i==1, twice with i==2, and once with i==3).

Why would this be happening? Why I am not receiving 3 prints from the parent (once when i==1, i==2, i==3). If we look at it from the parent's perspective - there is a while loop and we are supposed to hit the print statement inside only 3 times.

Is it perhaps that fork is async or something? What am I missing?

dbush
  • 205,898
  • 23
  • 218
  • 273
Dean Leitersdorf
  • 1,303
  • 1
  • 11
  • 22

3 Answers3

7

When you fork a process, the memory in parent is duplicated in the child. This includes the stdout buffer.

Starting on the second iteration of the loop in the parent, you have hello(1)7998, 8009 in the buffer. When the second child forks, this text is still in that child's buffer. The second child then prints hello(2)8010, 0, then a newline. The newline flushes the child's buffer, so the second child prints this:

hello(1)7998, 8009 hello(2)8010, 0 

After the fork, the parent prints hello(2)7998, 8010 which is added to the output buffer. Then on the third iteration, the third child inherits this and prints:

hello(1)7998, 8009 hello(2)7998, 8010 hello(3)8011, 0

The parent then prints hello(3)7998, 8011 which is again added to the buffer. Then parent then exits the loop and exits, after which the buffer is flushed and the parent outputs:

hello(1)7998, 8009 hello(2)7998, 8010 hello(3)7998, 8011

To correct this, then parent process needs to clear the output buffer before forking.

This can be done either by calling fflush(stdout) after calling printf, or by adding a newline (\n) to the end of the printf.

EDIT:

As a more general solution, calling fflush(NULL) will flush all open output streams.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • Excellent. Duplication of the buffer contents is the key. – John Bollinger Oct 05 '16 at 15:02
  • Wow.. Simply wow. – Dean Leitersdorf Oct 05 '16 at 15:02
  • 1
    `fflush(NULL)` flushes all open output files. – n. m. could be an AI Oct 05 '16 at 15:22
  • Hold on: Had I removed the fork, the parent would have just printed three times correct? Basically, the parent would have ignored what was in the buffer already. Then, since the child is an exact copy of the parent, how come the child doesn't "ignore" the buffer like the parent would? (this makes me think that "ignoring" the buffer is done outside the process - is that correct?) – Dean Leitersdorf Oct 05 '16 at 16:24
  • FYI, on most *nix systems `stderr` is unbuffered by default. Whenever I have possibly concurrent prints, I usually prefer using `fprintf(stderr, ...)` to make the output a bit less messy. – DaoWen Oct 05 '16 at 16:33
  • @DeanLeitersdorf The child isn't "ignoring" anything. It receives a copy of the parent's `stdout` buffer, then when the child's buffer is flushed (by printing a newline) it outputs whatever is in the buffer. – dbush Oct 05 '16 at 17:08
3

I think what you're seeing is merely that the process-local content-that-I-will-eventually-write-to-stdout buffer is being copied on fork, so contents queued prior to the fork are then output independently by several processes.

The forking is doing what you think, the instrument of measure is flawed.

I modified your code to add a flush:

    int ret = fork();
    printf("hello(%d)%d, %d ", i, getpid(), ret);
    fflush(stdout);

That ensures the result of the printf doesn't sit in a process-local buffer. It's written direct to stdout then and the local buffer is emptied. Output then became:

Starting pid:41731
hello(1)41731, 41732 hello(2)41731, 41733 hello(1)41732, 0 
hello(3)41731, 41734 hello(2)41733, 0 
hello(3)41734, 0 

i.e. the expected six calls.

(I used CodeRunner on a Mac to test this; it also shows the ostensibly nine results without the flush so I'm confident I duplicated the initial conditions).

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • Perfect! Thank you! What is the flush doing? – Dean Leitersdorf Oct 05 '16 at 14:50
  • @JohnBollinger at the same time, using the flush does solve it which is weird – Dean Leitersdorf Oct 05 '16 at 15:01
  • Just making sure that the process is writing everything it has to stdout then, rather than possibly keeping it in an internal cache. I'll wager the cache is being copied when you `fork`, so many of the `hello(1)7998`s aren't actually from process 7998, they were merely enqueued by process 7998 and then the enqueued text was copied over. Since `fflush` clears the buffers, there's nothing to copy. (EDIT: and answer modified, given that's a fairly compelling reason) – Tommy Oct 05 '16 at 15:09
0

Fork splits the process in two. In each of the child processes. You end up with 8 processes after 3 forks (unless you do something conditional)

Paul Stelian
  • 1,381
  • 9
  • 27