-1

I am a beginner and am trying to learn how to fork() and wait() functions work.

Can someone run my code and tell me what my output should be?

Right now I am getting: A B C A B C A D E

However, a buddy of mine says it should be: A B C A D E A B C

And another says it should be: A B C C D E

Because of the wait() functions, I thought the child processes had to finish before the parent. That is why I expect the output to end in an 'E'.

What would be some possible outputs then? I don't understand when I run it I get ABCABCADE. Shouldn't 'A' only ever be printed once for the initial child process?

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

int main(void) {
int pid;

    pid= fork();
    if (pid == 0) {
        fprintf(stdout, "A\n");
        pid= fork();
        if (pid==0) {
            fprintf(stdout, "B\n");
            pid=fork();
            fprintf(stdout, "C\n");
        }
        else {
            wait(NULL);
            fprintf(stdout, "D\n");
        }
    }
    else {
        fprintf(stdout, "E\n");
        wait(NULL);
    }
    // your code goes here
    return(0);
}
  • What did you write where `your code goes here'? The output isn't determinate anyway; you could get different results on different runs. Also, you can get different results again if you pipe the output or redirect the output to a file. – Jonathan Leffler Feb 21 '17 at 15:02

3 Answers3

1

There's no reason that E should appear last, because you didn't wait() until after printing the E.

There's an additional complication that you are not necessarily using line-buffered output, and if there is any pending output before you fork, then both parent and child will output the buffered text.

Let's add fflush(stdout); before each fork(). If we do that, we'll get rid of the multiple A outputs and we can reason about the rest. Here's a time-line:

parent
 |
 |
 +------\
 |      |
"E"    "A"
 |      |
wait    +------\
 .      |      |
 .     wait   "B"
 .      .      |
 .      .      +------\
 .      .      |      |
 .      .     "C"    "C"
 .      .      |
 .      |<----exit
 .     "D"
 .      |
 |<----exit
 |

You can see that E could be printed at any point, but D will not be printed until after at least one C (the left-hand one).

If you swapped the order of

    fprintf(stdout, "E\n");
    wait(NULL);

you could ensure that E always comes after D (which in turn is after at least one C), but the other C could still be last, as there's no ordering relation with that process's exit.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
0

It is not specified whether parent or child runs first when you fork(), or anyway how long that process runs or how far it gets before the other takes over, or whether the two in fact run concurrently on different cores. If the parent successfully wait()s for its child then as soon as that wait() returns it is certain that the child has terminated. Absent other means of synchronization, however, it is impossible to predict the order of the parent's actions between fork()ing and collecting that child via wait() relative to the actions performed by the child.

Observe also the conditionals involving the return values of the fork() calls. A successful fork() returns 0 in the child (only), so these tie most of your program's behaviors to exactly one process each.

But there is also another factor here: the interaction of multiple handles on the same open file description. When you fork(), you end up with two stdout streams (both referring to the same open file description) where previously you had one. POSIX places some restrictions on how programs must handle that situation. In the event that your program's standard output is line-buffered, which is the default when it is connected to a terminal, program behavior is well defined on account of the newlines at the end of each string to be printed. If stdout is fully-buffered, however, as is likely when it is connected to a pipe, then you must fflush(stdout) before forking to have defined behavior. It's safest, therefore, to fflush() before forking, ensuring that program behavior is defined regardless of execution environment.

If you analyze the program in light of these considerations and supposing that your program is run in a way that gives it defined behavior at all, you will see that there are multiple possible outputs, but your proposal is not among them. If your program is run in a way that makes its behavior undefined, nothing can be said about the output.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • What would be some possible outputs then? I don't understand when I run it I get ABCABCADE. Shouldn't 'A' only ever be printed once for the initial child process? – SuperHippo Feb 21 '17 at 15:19
  • @SuperHippo, this is a good point, which requires an update to my answer, which you will see presently. The bottom line is that your program exhibits not just un*specified* behavior but full-blown un*defined* behavior. – John Bollinger Feb 21 '17 at 15:24
0

The output isn't completely determinate, so you can potentially get different results on different runs. Also, if you pipe the output, or redirect it to a file, you get a different result from if you do not; see printf() anomaly after fork() for the full details.

Here's a revision of your code which can force-flush the output (it uses the POSIX standard <sys/wait.h> header instead of the non-standard <wait.h> header, too.

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

static int flush = 0;

static void print(const char *str)
{
    printf("%s\n", str);
    if (flush)
        fflush(stdout);
}

int main(int argc, char **argv)
{
    if (argc > 1)
        flush = (argv[argc] == 0);
    int pid = fork();
    if (pid == 0)
    {
        print("A");
        pid = fork();
        if (pid == 0)
        {
            print("B");
            pid = fork();
            print("C");
        }
        else
        {
            wait(NULL);
            print("D");
        }
    }
    else
    {
        print("E");
        wait(NULL);
    }
    return(0);
}

Most often for me, when that is run free (no redirection, no command line arguments), the E appears first:

E
A
B
C
C
D

When piped to cat, the output varies, but becomes variations on the theme:

A
B
C
A
D
A
B
C
E

and

A
B
C
A
B
C
A
D
E

When run with an argument, to force the flushes to occur, I get this consistently:

E
A
B
C
D
C

The scheduler is allowed to run the processes in different sequences; the scheduler on my machine does run the processes in different orders. The parent process usually gets to run until until it hits the wait() but the children don't get scheduled immediately, so its output can appear before any of the children.

YMMV.

Tested on a Mac (macOS Sierra 10.12.3, GCC 6.3.0).

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278