6

When the following code runs, I understand the parent and child both will run in parallel immediately after fork() is called.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    int pfds[2];
    char buf[30];

    pipe(pfds);

    if (!fork()) {
        printf(" CHILD: writing to the pipe\n");
        write(pfds[1], "test", 5);
        printf(" CHILD: exiting\n");
        exit(0);
    } else {
        printf("PARENT: reading from pipe\n");
        read(pfds[0], buf, 5);
        printf("PARENT: read \"%s\"\n", buf);
        wait(NULL);
    }

    return 0;
}

That means child will execute:

printf(" CHILD: writing to the pipe\n"); 

And the parent will execute

printf("PARENT: reading from pipe\n");

In parallel.

For those not familiar with C, in sh:

$ sh -c 'echo CHILD & echo PARENT; wait'
PARENT
CHILD

So I ran this code on a single core cpu and also on dual core, but I noticed that parent is always getting printed first. Since the two are in parallel, it is quite reasonable to expect random order. But it is not happening like that. Why is it like that?

user25108
  • 383
  • 5
  • 15
  • 1
    Is this question appropriate here or should it be moved to stackoverflow? – vfbsilva Feb 05 '14 at 12:30
  • 1
    It's appropriate here, it's not a programming question, it's a question about a given Unix/Linux scheduler behaviour. – Stephane Chazelas Feb 05 '14 at 13:01
  • 1
    On Linux 3.12, I can get the child to run first sometimes with `sh -c 'sleep 1; echo CHILD & echo PARENT; wait'` (that is, make the sh process idle for some time before forking) if I `sudo sysctl -w kernel.sched_child_runs_first=1` and have a few other CPU intensive processes running at the same time. The scheduling algorithm is something quite complex taking many parameters in consideration. I'd say it's quite pointless to have any expectation here. – Stephane Chazelas Feb 05 '14 at 13:19
  • You should rewrite your question and remove the `pipe` aspect as it's not relevant here to avoid confusion. – Stephane Chazelas Feb 05 '14 at 13:51
  • @user25108 take into account any shell may be _yielding_ parent, child, both, or none, after the fork operation. – 1737973 Feb 05 '14 at 15:10

2 Answers2

5

Apparently whatever scheduler you're running decides, and it can vary.

I can say from experience that if you assume that one of the two processes always runs first, you will introduce some very subtle race conditions. Either synchronize on something, like a special message on the pipe, or don't assume that either one runs first.

  • 1
    +1 it depends on the scheduler in which order it wants to execute the parent and child process. – Vishal R Feb 05 '14 at 19:20
  • why cant the scheduler run the processes in parallel especially since we are having multicores now ? – user2799508 Feb 06 '14 at 08:16
  • @user2799508 - a scheduler on a multi-core machine certainly can do that. My point is that you just can't tell. Don't assume. Either synchronize on something, directory creation maybe, or write code that is "thread safe" with respect to operations that can effect the other process. –  Feb 06 '14 at 13:55
2

I recognise that this might not exactly answer the question, but it may serve to shed some light into what is happening:

if (!fork()) {
        printf(" CHILD: writing to the pipe\n");
        write(pfds[1], "test", 5);
        printf(" CHILD: exiting\n");
        exit(0);
    } else {
        printf("PARENT: reading from pipe\n");
        read(pfds[0], buf, 5);
        printf("PARENT: read \"%s\"\n", buf);
        wait(NULL);
    }

After fork has been executed, the parent process continues to run, which then proceeds to the else statement, and executes the printf function. After that it attempts to read from the pipe, but it blocks because there is no data in the pipe. That's right, read() blocks when it attempts to read from a pipe without data.

Documentation that serves to prove this is available. From the xv6 documentation:

If no data is available, a read on a pipe waits for either data to be written or all file descriptors referring to the write end to be closed; in the latter case, read will re- turn 0, just as if the end of a data file had been reached.

And while xv6 may not be Linux, it serves as a design guide to UNIX. If you don't consider this valid enough, then the linux Man pages on pipes can shed some light:

If a process attempts to read from an empty pipe, then read(2) will block until data is available. If a process attempts to write to a full pipe (see below), then write(2) blocks until sufficient data has been read from the pipe to allow the write to complete. Nonblocking I/O is possible by using the fcntl(2) F_SETFL operation to enable the O_NONBLOCK open file status flag.

After that, control is passed to the child, which proceeds to execute its version of the printf() function, write()s to the pipe, and then prints an exiting message, finally exiting after that.

When the child has exited, control is again passed to the parent process, which finds read() on the pipe to be able to read data, which allows it to finish what its work.

So as far as the

In parallel

part is concerned, it doesn't seem to be in parallel exactly. It's serial, it's just very fast for us to notice that it's done in a serial order of execution.

NlightNFotis
  • 9,559
  • 5
  • 43
  • 66
  • Its not parallel you are right @NlightNFotis. But the execution (child will execute first or parent) depends on Scheduler. – Vishal R Feb 05 '14 at 19:15
  • @vishram0709 yes, I am aware of this. I thought I had already mentioned it, but on retrospect, I don't see it mentioned anywhere. I somehow missed to mention it. – NlightNFotis Feb 05 '14 at 19:41
  • `fork(2)` has three types of return values you should handle <0, ==0 and >0 – aggsol Feb 27 '20 at 09:16